[學習筆記] 網路流最大流(Dinic演算法)
網路流最大流
Dinic 演算法
先回憶一下\(EK\)演算法,\(bfs\)遍歷整張圖直到找到一條增廣路,每次只能找一條,速度就比較慢,所以我們就得使用
更好的\(dinic\)演算法,\(EK\)一次找一條,但是源點到匯點可能有很多條最短路經,所以我們可以一次性全部擴充套件
首先我們需要知道分層圖:滿足\(d[y]=d[x]+1d[y]=d[x]+1\)的邊\((x,y)\)構成的子圖被稱為分層圖
\(d[x]\)表示的就是點\(x\)的層次,也就是\(S\)到\(x\)最到需要經過的邊數(和流量沒有關係)
至於為什麼要分層,問就是防止\(dfs\)亂走
演算法流程
先進行一邊\(bfs\)
每次\(dfs\),從\(S\)開始攜帶\(∞\)的流往下遞迴,遞迴時列舉當前節點的所有相鄰的點,並儘可能將有的流發給相鄰的點
並繼續遞迴,遞迴結束後更新現有的流,如果沒有流或者所有相鄰的點都訪問完了就直接返回,如果到了匯點\(T\)
就把所有的現有的流都送出去
簡單來說就是
- 先\(bfs\)求出每個點的\(d_x\)給每個點一個層次,得到一張分層圖
- 根據層次反覆\(dfs\),每次嘗試給予時,只考慮給予自己下一層的點,就可以防止混亂
當前弧優化
從前面的介紹可以知道,這個\(dinic\)
對於一個節點\(x\),由於有效邊構成的字圖一定是\(DAG\)(有向無環圖),所以走到第\(i\)條弧時,前\(i-1\)條弧到匯點
的流都一定流滿了,沒有可行的路線了,也就是說每處理完一條從\(x\)出發的弧,以後就不用在訪問了
於是我們考慮開一個數組\(now_x\)表示當前考慮到了哪一條弧(不是某個點),使用方法和\(head\)是相同的,這樣就
可以改變每次搜尋的起點了,由於大前提是在分層圖中,所以每次\(bfs\) 就把\(now_x\)設為鄰接表的表頭
\(Q:\)為什麼當前弧優化不寫成\(now[u]=nxt[i]\)
\(A:\)除非當前這條邊到源點了或者該流的都流完了,否則都有多流一些的可能,而且從名字“當前”也能看出吧(
code
#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>
#include <map>
#include <queue>
#define int long long
using namespace std;
const int inf=0x3f3f3f3f;
const int N=1e4+10;
int n,m,s,t,head[N],nxt[N],val[N],tot=1,d[N],now[N],ver[N];
//注意tot的初始值要設為1,因為0^1=0
inline int read(){
int x=0,f=1;char ch=getchar();
while(ch>'9'||ch<'0'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+ch-'0';ch=getchar();}
return x*f;
}
inline void add(int x,int y,int z){
ver[++tot]=y,nxt[tot]=head[x],val[tot]=z,head[x]=tot;
}
inline bool bfs(){
for(int i=1;i<=n;i++) d[i]=inf,now[i]=head[i];
queue<int> q;
q.push(s);
d[s]=0;
while(!q.empty()){
int u=q.front();q.pop();
for(int i=head[u];i;i=nxt[i]){
int v=ver[i];
if(val[i]&&d[v]==inf){//滿足增廣路的性質且沒有被訪問過
q.push(v);
d[v]=d[u]+1;//分層
if(v==t) return 1;//找到增廣路
}
}
}
return 0;
}
inline int dinic(int u,int sum){//求單次增廣送出去的流量,sum
if(u==t) return sum;//到匯點,直接把全部送出去
int ret=0;//目前送出去多少流
for(int i=now[u];i;i=nxt[i]){
now[u]=i;//當前弧優化
int v=ver[i];
if(val[i]&&d[v]==d[u]+1){//只考慮下一層的點且滿足增廣路的性質
int k=dinic(v,min(sum-ret,val[i]));//k是v送出去的流
val[i]-=k;val[i^1]+=k;//遞迴更新現有的流
ret+=k;
if(ret==sum) return sum;//如果全部送完就直接返回
}
}
return ret;
}
signed main(){
n=read();m=read();s=read();t=read();
for(int i=1;i<=m;i++){
int u=read(),v=read(),w=read();
add(u,v,w);
add(v,u,0);
}
int res=0;
while(bfs()) res+=dinic(s,inf);
cout<<res<<endl;
return 0;
}