網路流-最大流
【前言】
網路流作為一個經典問題,在OI及實際生活中有著廣泛的應用,值得我們仔細研究。
【何為網路流?】
網路流,是一種資源調配問題,如下圖(以下圖片均來自網路):
其中,S表示網路流中的源點,是資源的唯一出發點。
T表示網路流中的終點,是各種資源的目的地。
正如水管有粗細之分,道路有寬窄之分,
網路流中的每條邊(這裡稱為弧)都有一個容量cap,表示單位時間最多能通過的資源量。
同時,每條弧都有一個流量flow,表示當前單位時間通過的資源量。
上圖是流的一個可行方案。我們的目的就是要求最大流(單位時間到達終點的資源最大)
【後向弧】
我們定義後向弧為與前向弧頂點相同,方向相反的弧。且後向弧的流量永遠為所對應前向弧的流量的相反數。(這裡推薦一個實用方法:把邊從2開始編號,那麼x與x^1就是一對前向/後向弧)
【增廣路】
所謂增廣路,就是能使當前結果更優的一條路。在最大流中,增廣路的定義如下:
1.增廣路由源點出發,終點結束
2.增廣路走過的每一條弧中,cap>flow(即這條路必須所有弧都可以增加流量)
3.增廣路可以經過前向弧,也可以經過後向弧。
上圖是一條增廣路。其中BC是一條後向弧。同時也發現原來的狀態不是最大流。
我們可以發現,後向弧為演算法提供了糾正原來錯誤的可能。
【FF方法】
解決最大流的常見方法是FF方法,FF方法可以由很多不同搜尋演算法實現,衍生出很多不同的演算法。
FF方法的過程:
1.初始時,各弧流量為0。
2.尋找一條增廣路,找不到增廣路則說明已經獲得最大流,退出
3. 若找到增廣路,取路上各弧的最小殘餘流量(cap-flow),記為Min
4.把增廣路上各弧流量+Min,其對應弧流量作相應修改
5.重複2~4步驟,直至找不到增廣路
我們可以發現,FF方法其實就是一個不斷糾正,不斷優化的過程。其中尋找增廣路是次方法的時間瓶頸。
【EK演算法】
實際應用時,我們常用BFS來尋找增廣路,稱為EK演算法。
bool bfs(){
memset(vis,0,sizeof(vis));
int hed=0,til=1;
que[1]=s;vis[s]=1;
while (hed!=til)
for (int j=a.lnk [que[++hed]];j;j=a.nxt[j])
if (!vis[a.son[j]]&&a.cap[j]>a.flw[j]){
que[++til]=a.son[j];vis[a.son[j]]=1;
fa[a.son[j]]=que[hed];id[a.son[j]]=j;
if (a.son[j]==t) return 1;
}
return 0;
}
void EK(){
while (bfs()){
int Min=0x3f3f3f3f;
for (int x=t;x!=s;x=fa[x])
Min=min(Min,a.cap[id[x]]-a.flw[id[x]]);
ans+=Min;
for (int x=t;x!=s;x=fa[x])
a.flw[id[x]]+=Min,a.flw[id[x]^1]-=Min;
}
}
顯然,在最壞情況下,時間複雜度
【Dinic演算法】
實際運用中,我們還有一個更為快捷的演算法求解最大流問題,就是Dinic。
【層次圖】
我們把源點到i所經過的最少邊數稱為i的距離。
距離相同的點歸為同一個層次。
在殘量網路中,只留下連向下一個層次的邊,就構成了一個層次圖。
【Dinic流程】
首先,如果有層次圖,就說明當前解還可以增廣
用BFS刷出層次圖,然後就可以直接DFS增廣
BFS很簡單,直接遍歷一下就可以了,不用多說
DFS的過程,可以理解為有一杯無限多的水,不斷從源點倒入
模擬水流的擴張,遍歷整個子圖,並返回當前子圖能進入的最大流量
程式碼是這樣的:
int dfs(int x,int flow){
if (x==T||flow==0) return flow;
int res=0,f;
for (int j=lnk[x];j;j=nxt[j])
if (d[x]+1==d[son[j]]&&(f=dfs(son[j],min(flow,cap[j]-flw[j])))>0){
flw[j]+=f;flw[j^1]-=f;
res+=f;flow-=f;
if (flow==0) break;
}
return res;
}
【當前弧優化】
其實,上面給出的程式碼並不是效率最高的。
試想,對於點x,它的一個兒子s如果已經被DFS增廣過了
那麼下次增廣x的時候,就沒有必要再來增廣s。
因為DFS增廣儘可能地使流量最大,再次DFS就不會再有增廣的可能
所以我們每次建立lnk[]的一個副本pos[]
那麼直接修改pos[]就能達到目的
正確姿勢:
int dfs(int x,int flow){
if (x==T||flow==0) return flow;
int res=0,f;
for (int &j=pos[x];j;j=nxt[j])
if (d[x]+1==d[son[j]]&&(f=dfs(son[j],min(flow,cap[j]-flw[j])))>0){
flw[j]+=f;flw[j^1]-=f;
res+=f;flow-=f;
if (flow==0) break;
}
return res;
}
完整模板如下(hihoCoder 1369):
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn=505,maxe=40004,INF=0x3f3f3f3f;
int n,e,S,T;
int tot,lnk[maxn],nxt[maxe],cap[maxe],flw[maxe],son[maxe];
void add(int x,int y,int w){
son[++tot]=y;cap[tot]=w;flw[tot]=0;nxt[tot]=lnk[x];lnk[x]=tot;
}
inline int red(){
int tot=0,f=1;char ch=getchar();
while (ch<'0'||'9'<ch) {if (ch=='-') f=-f;ch=getchar();}
while ('0'<=ch&&ch<='9') tot=tot*10+ch-48,ch=getchar();
return tot*f;
}
int d[maxn],pos[maxn],que[maxn];
bool bfs(){
memset(d,63,sizeof(d));
int hed=0,til=1;
d[S]=0;que[1]=S;
while (hed!=til)
for (int j=lnk[que[++hed]];j;j=nxt[j])
if (d[son[j]]==INF&&flw[j]<cap[j])
que[++til]=son[j],d[son[j]]=d[que[hed]]+1;
return d[T]!=INF;
}
int dfs(int x,int flow){
if (x==T||flow==0) return flow;
int res=0,f;
for (int &j=pos[x];j;j=nxt[j])
if (d[x]+1==d[son[j]]&&(f=dfs(son[j],min(flow,cap[j]-flw[j])))>0){
flw[j]+=f;flw[j^1]-=f;
res+=f;flow-=f;
if (flow==0) break;
}
return res;
}
int main(){
n=red(),e=red();S=1;T=n;tot=1;
for (int i=1,x,y,z;i<=e;i++)
x=red(),y=red(),z=red(),add(x,y,z),add(y,x,0);
int ans=0;
while (bfs()){
memcpy(pos,lnk,sizeof(lnk));
ans+=dfs(S,INF);
}
printf("%d",ans);
return 0;
}