最大流:
定義:
有一張圖,要求從源點流向匯點的最大流量(可以有很多條路到達匯點)
概念:
殘量網絡:
定義 \(c_f(u,v)\) 為邊的流量之差,表示這條邊的容量和流量之差,即:
對於流函數 \(f\) ,殘存網絡 \(G_f\) 是網絡 \(G\) 中 所有節點和剩余容量大於 \(0\) 的邊 構成的子圖。
形式化的定義: \(G_f=(V_f=V,E_f=\left\{(u,v)\in E,c_f(u,v)>0\right\})\)
注意:剩余容量大於零的邊可能不在原圖 \(G\) 中。
殘量網絡中包括了那些還剩了流量空間的邊構成的圖,也包括反向邊。
增廣路:
在原圖 \(G\) 中若一條從源點到匯點的路徑上所有邊的 剩余容量大於零,這條路就被稱為增廣路。
或者說,在殘存網絡 \(G_f\) 中,一條從源點到匯點的路徑被稱為增廣路.

在這張圖中,從 \(4 \rightarrow 3\), 先走 \(f_(4,3)=20\) 這條邊。
然后判斷 \(4 \rightarrow 2 \rightarrow 3\) 這條增廣路 的總流量為 \(20\), \(4 \rightarrow 2 \rightarrow 1 \rightarrow 3\) 這條路總流量為 \(30\).
因此最大流就是 \(20+30=50\)
算法:
\(EK\) 算法:
思想:用 \(BFS\) 不斷尋找增廣路,直到網絡上不存在增廣路為止。
分析:
每輪尋找增廣路的過程中,只考慮圖中所有 \(f(u,v)<c(u,v)\) 的邊,用 \(BFS\) 找到任意一條 \(s \rightarrow t\) 的路徑,同時計算出路徑上個邊的剩余容量的最小值 \(minn\) ,則網絡的流量可以增加 \(minn\)。
同時,當一條邊流量 \(f(u,v)>0\) 時,我們需要建立反向邊 \(f(v,u)=-f(u,v)\) ,此時必定有 \(f(y,x)<c(y,x)\) 。
在該算法中,我們需要遍歷正反邊,利用 鄰接表成對存儲 的技巧,也就是
\(2^1=3,3^1=2\) 這樣能相互得到,存儲標號從 \(2\) 開始。
對於每條邊,我們只記錄剩余容量 \(c(u,v)-f(u,v)\) 。
當一條邊經過一條流量為 \(e\) 的邊,令 \((u,v)\) 的剩余流量減少 \(e\) ,\((v,u)\) 的剩余容量增加 \(e\)。
想法就像這張圖:

時間復雜度為 \(O(nm^2)\) ,時間復雜度較高
代碼:
#include<bits/stdc++.h>
using namespace std;
const int N=1005,M=2e5+5,inf=0x3f3f3f3f;
#define ll long long
int head[N],nxt[M],ver[M],tot=1,edge[M];
int n,m,s,t;
ll maxflow;
int vis[N],incf[N],pre[N];
void add(int x,int y,int z){
ver[++tot]=y; nxt[tot]=head[x]; head[x]=tot; edge[tot]=z;
ver[++tot]=x; nxt[tot]=head[y]; head[y]=tot; edge[tot]=0;
}
void update(){
int x=t;
while(x!=s){
int i=pre[x];
edge[i]-=incf[t]; edge[i^1]+=incf[t];
x=ver[i^1]; //繼續回溯
}
maxflow+=incf[t];
}
bool bfs(){
memset(vis,0,sizeof(vis)); queue<int> q; q.push(s); vis[s]=1;
incf[s]=inf;//各邊剩余最小容量
while(!q.empty()){
int x=q.front(); q.pop();
for(int i=head[x]; i; i=nxt[i]){
int y=ver[i],z=edge[i];
if(vis[y]||!z) continue;
incf[y]=min(incf[x],z);//限制流量和當前點能流的最大流量比較
pre[y]=i;//記錄前驅,用來找到最長路的實際方案
q.push(y); vis[y]=1;
if(y==t) return 1;
}
}
return 0;
}
int main(){
cin>>n>>m>>s>>t;
for(int i=1,x,y,z;i<=m;i++){
scanf("%d%d%d",&x,&y,&z); add(x,y,z);
}
while(bfs()) update();//證明有一條新邊可以更新
cout<<maxflow<<endl;
system("pause");
return 0;
}
\(Dinic\) 算法:
\(EK\) 每輪會遍歷整個殘量網絡,但只能找出一條增廣路,屬實是拉了屬於是
我們可以利用 分層圖 的性質(即滿足 \(dep[u]+1=dep[v]\) 的邊 \((u,v)\) 構成的子圖) 。
流程:
-
在殘量網絡上 \(BFS\) 求出節點的層次,構造分層圖。
-
在分層圖上 \(DFS\) 尋找增廣路,在回溯時實時更新剩余容量,另外,各個點可以流向多條出邊,同時還需要剪枝等操作。
時間復雜度為 \(O(n^2m)\) ,實際上會更優。
當前弧優化:
每次增廣一條路后可以看做 榨干 了這條路,既然榨干了就沒有再增廣的可能了。
但如果每次都掃描這些枯萎的邊是很浪費時間的。
那我們就記錄一下榨取到那條邊了,然后下一次直接從這條邊開始增廣,就可以節省大量的時間。這就是 當前弧優化。
具體怎么實現呢,先把鏈式前向星的 \(head\) 數組復制一份,存進 \(cur\) 數組,然后在 \(cur\) 數組中每次記錄“榨取”到哪條邊了。
代碼:
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int inf=0x3f3f3f3f,N=5005,M=200005;
int nxt[M],ver[M],tot=1,edge[M],head[N];
int n,m,s,t;
ll maxflow;
int dep[N],incf[N],pre[N],cur[N];
void add(int x,int y,int z){
ver[++tot]=y; edge[tot]=z; nxt[tot]=head[x]; head[x]=tot;
ver[++tot]=x; edge[tot]=0; nxt[tot]=head[y]; head[y]=tot;
}
bool bfs(){
memset(dep,0,sizeof(dep)); queue<int> q; q.push(s); dep[s]=1;
while(!q.empty()){
int x=q.front(); q.pop();
for(int i=head[x];i;i=nxt[i]){
int y=ver[i],z=edge[i];
if(dep[y]||!z) continue;
q.push(y);
dep[y]=dep[x]+1;
if(y==t) return 1;
}
}
return 0;
}
int dinic(int x,int flow){//flow表示限制流量
if(x==t) return flow;
int totflow=0;//從這個點總共可以增廣多少流量
for(int i=cur[x];i;i=nxt[i]){
cur[x]=i;//記錄上一次增廣到哪條邊了
int y=ver[i],z=edge[i];
if(dep[y]!=dep[x]+1||!edge[i]) continue;
int canflow=dinic(y,min(flow,z));//表示這條增廣路上能增加多少流量
edge[i]-=canflow; edge[i^1]+=canflow;
totflow+=canflow;//總共增加多少流量
flow-=canflow;
if(flow<=0) break;//當前點已經沒有流量
}
return totflow;
}
int main(){
cin>>n>>m>>s>>t;
for(int i=1,x,y,z;i<=m;i++){
scanf("%d%d%d",&x,&y,&z); add(x,y,z);
}
while(bfs()){
memcpy(cur,head,sizeof(head));
maxflow+=dinic(s,inf);
}
cout<<maxflow<<endl;
system("pause");
return 0;
}
