EK算法流程
EK算法的流程很簡單:
- 隨意找一個可行流作爲流量網絡更新的基礎(一般題目沒有規定可以採用流量爲0的可行流)
- 利用 找一條從源點到匯點的可行流路徑
- 用新找到的可行流路徑更新原有流量網絡:先找到該可行流路徑中流量最小邊,然後將該路徑上所有正向邊都減去該最小邊的流量,反向邊都加上該最小邊的流量(想想,爲什麼要設反向邊)
- 不斷重複2和3兩個步驟,直到在第2步的時候找不到一條從源點到匯點的可行流路徑(已經達到最大流的流量網絡滿足的特徵是,源點能達到的所有點記作集合s,不能達到的所有點記作集合t,那麼從s到t的所有邊流量都爲0,t到s的所有邊的流量之和即爲最大流)
反向邊的作用
反向邊的作用,用一個詞概括:後悔藥
如果沒有反向邊,那麼我們隨意找到一個可行流,例如這個:
我們很容易找到一個可行流:1->2->5->6,流量爲2。
到這一步之後我們發現我們無法再進行增廣。
如果這時候我們添加了反向邊,就會是這樣:
那麼通過反向邊,我們可以又找到這麼一條可行流路徑:1->4->5->2->3->6,流量爲1.
所以最後的最大流是3。
有了反向邊,我們可以做到這件事情:如果我們發現某一條邊上分配流量過多,不利於最優方案的推出,我們利用反向邊可以反悔。
所以反向邊增大的意義就在於正向邊的流量減小,也就是“退流”。
所以我們可以發現,始終滿足:正向邊流量+反向邊流量=該邊容量
代碼實現
#include<bits/stdc++.h>
using namespace std;
inline int read(){
int num=0;char c=' ';bool flag=true;
for(;c>'9'||c<'0';c=getchar())
if(c=='-')
flag=false;
for(;c>='0'&&c<='9';num=(num<<3)+(num<<1)+c-48,c=getchar());
return flag ? num : -num;
}
const int maxn=10020;
const int maxm=100020;
const int INF=1e8;
namespace Graph{
int n,m,s,t;
int top=0,head[maxn];
struct node{
int dot,val,next;
}a[maxm<<1];//鄰接表
void insert(int x,int y,int w){
a[top].dot=y;
a[top].val=w;
a[top].next=head[x];
head[x]=top++;
}//插入邊
void init(){
n=read();m=read();//點數、邊數
s=read();t=read();//源點、匯點
memset(head,-1,sizeof head);
for(int i=1;i<=m;i++){
int x=read(),y=read(),v=read();
insert(x,y,v);
insert(y,x,0);//插入反向邊爲
}
}
}using namespace Graph;
namespace Flow{
struct flow{
int edge,v;
}pre[maxn];
//記錄了某點在某一條可行流中
//它的前驅點和連接它和它的前驅的有向邊的編號
bool vis[maxn];//是否被訪問過
bool bfs(){
queue<int>q;
memset(vis,0,sizeof vis);
memset(pre,-1,sizeof pre);//初始化
q.push(s);
vis[s]=true;
pre[s].v=s;
while(!q.empty()){
int u=q.front();
q.pop();
for(int i=head[u];i!=-1;i=a[i].next){
int v=a[i].dot;
if(!vis[v]&&a[i].val){
//如果該邊的流量不爲0或者沒有訪問過
pre[v].v=u;
pre[v].edge=i;//記錄前驅和邊
vis[v]=true;//標記訪問過
if(v==t)return true;
//如果到匯點了,那麼這個可行流路徑算是找到了
q.push(v);
}
}
}
return false;//找不到
}
void EK(){
int ans=0;
while(bfs()){//當可以找到的時候
int flow=INF;
for(int i=t;i!=s;i=pre[i].v){
flow=min(flow,a[pre[i].edge].val);
}//找到最小流量
ans+=flow;//本次增廣收益哈哈哈
for(int i=t;i!=s;i=pre[i].v){
int edge=pre[i].edge;
a[edge].val-=flow;//正向邊表示的是殘餘網絡
a[edge^1].val+=flow;//反向邊表示的是流量
//始終保持和相加等於容量
}
}
printf("%d\n",ans);
}
}using namespace Flow;
int main(){
init();
EK();
return 0;
}
時間複雜度
時間複雜度 但是,一般不會更新那麼多次。
一般能處理 到 規模的網絡。