引入
最大流算法分為兩類,一種是增廣路算法,一種是預留推進算法。增廣路算法包括時間復雜度\(O(nm^2)\)的EK算法,上界為\(O(n^2m)\)的Dinic算法,以及一些其他的算法。EK算法直接進行增廣,而Dinic則是通過沿着最短路增廣優化了復雜度,它的做法是每次進行bfs求出層次圖,再dfs沿着層次圖進行多路增廣。然而,Dinic中每次進行bfs求層次圖似乎有點浪費,因為層次圖的改動並不是非常大。在這種思路下,我們考慮直接在每次dfs增廣的時候修改層次圖來優化求最短路的過程。
算法
ISAP (Improved Shortest Augment Path) 算法其實是通過dfs中不斷修改距離標號\(d\)的方式省去了每次的bfs,所以稱為improved。
Dinic算法中,我們需要每次搜索出層次圖,而在ISAP中,我們只需要每次dfs的過程中修改距離標號。具體來說,我們用\(d[x]\)表示殘余網絡上\(x\)到匯點\(t\)的最短距離,我們每次沿着\(d[x]=d[v]+1\)的路增廣。如果點\(x\)的出邊的點沒有發現滿足這個條件的那么就說明當前的最短路已經過時了,我們需要修改距離標號。修改距離標號,就是讓\(x\)可以至少有一個點可以增廣,所以取所有\(d[v]\)中最小的那個加一即可。這樣增廣下去,當\(d[s]\),即\(s\)到\(t\)的距離大於等於\(n\)的時候,就說明至少有一個點經過了兩次,即不存在增廣路了,這個時候算法退出。
復雜度
ISAP的復雜度上界其實就是Dinic復雜度去掉bfs的部分。接下來我們證明Dinic的復雜度,再說明ISAP的復雜度上界。
Dinic算法每次求出層次圖,然后dfs多路增廣。可以發現,當dfs退出的時候,可以增廣的所有路徑都已經增廣完了。考慮每一次\(t\)的層次標號,可以發現,如果一次dfs完,下一次bfs得到\(t\)的層次標號與上一次一樣,那么就是說,上一次還有沒有增廣完的路徑,這顯然是不可能的。那么\(t\)的層次標號是否可能減少呢?如果可以的話,那么上一次也可以找到更短的路徑,因為殘余網絡的增廣會把邊變成反向。這樣,每次bfs的時候得到的\(t\)層次編號是嚴格遞增的,也就是說,\(n\)次階段以內一定可以結束算法。bfs的復雜度為\(O(m)\),dfs的復雜度看似是\(O(m)\)的,其實不然。考慮一個滿足\((u,v,w)\),其中\(w\)為\(v\)可以到達的點的數量的網絡流原圖,那么一次dfs可以達到\(nm\)。這樣看來,整個過程分成了\(n\)個階段,每一階段為\(O(nm)\),所以總共是\(O(n^2m)\)。
ISAP的復雜度上界與這個是相同的。
優化
接下來我們將看到ISAP是如何被極大地優化的。
- 如果一開始把距離標號都設為0,那么dfs最多需要\(O(n^2)\)來把距離標號初始化,而我們可以最開始逆向bfs一次,\(O(n+m)\)初始化所有距離標號。
- 如果距離標號出現了斷層,那么顯然不存在新的增廣路。我們用一個gap數組記錄每種層次標號有多少個,如果當前修改了最后一個某種層次標號,那么就出現了前后斷層,結束算法。
- 增廣過程中,如果一個點的標號沒有修改過,那么它已經遍歷過的邊不需要再遍歷一次,所以我們存下每次遍歷到哪條邊,下一次從這條邊開始遍歷(因為有可能到這里之后流量用完了,但是還沒有增廣完)。
- 最短路的修改具有連續性,即我們不需要每次求后繼的標號最小值,而是直接給標號加一。
- 同Dinic中,如果流量用完了直接退出。
ISAP非常好地結合標號的思想優化了SAP算法,編程復雜度極低(我的遞歸版主過程只有30行),可以作為網絡流算法的首選。
UPDATE:誰能告訴我ISAP在什么數據下會被卡掉!!
代碼
poj1273網絡流模板題。
#include<cstdio>
#include<cctype>
#include<cstring>
#include<vector>
#include<algorithm>
using namespace std;
int read() {
int x=0,f=1;
char c=getchar();
for (;!isdigit(c);c=getchar()) if (c=='-') f=-1;
for (;isdigit(c);c=getchar()) x=x*10+c-'0';
return x*f;
}
const int maxn=205;
const int maxm=205;
const int inf=2e9+7;
struct edge {
int v,w,nxt;
} e[maxm<<1];
int h[maxn],tot,n,m,gap[maxn],last[maxn],d[maxn],que[maxn],ql,qr;
vector<int> inv[maxn];
void add(int u,int v,int w) {
e[++tot]=(edge){v,w,h[u]};
h[u]=tot;
e[++tot]=(edge){u,0,h[v]};
h[v]=tot;
}
void init(int s,int t) {
memset(gap,0,sizeof gap),memset(d,0,sizeof d),++gap[d[t]=1];
for (int i=1;i<=n;++i) last[i]=h[i];
que[ql=qr=1]=t;
while (ql<=qr) {
int x=que[ql++];
for (int i=h[x],v=e[i].v;i;i=e[i].nxt,v=e[i].v) if (!d[v]) ++gap[d[v]=d[x]+1],que[++qr]=v;
}
}
int aug(int x,int s,int t,int mi) {
if (x==t) return mi;
int flow=0;
for (int &i=last[x],v=e[i].v;i;i=e[i].nxt,v=e[i].v) if (d[x]==d[v]+1) {
int tmp=aug(v,s,t,min(mi,e[i].w));
flow+=tmp,mi-=tmp,e[i].w-=tmp,e[i^1].w+=tmp;
if (!mi) return flow;
}
if (!(--gap[d[x]])) d[s]=n+1;
++gap[++d[x]],last[x]=h[x];
return flow;
}
int maxflow(int s,int t) {
init(s,t);
int ret=aug(s,s,t,inf);
while (d[s]<=n) ret+=aug(s,s,t,inf);
return ret;
}
int main() {
#ifndef ONLINE_JUDGE
freopen("test.in","r",stdin);
#endif
while (~scanf("%d%d",&m,&n)) {
tot=1,memset(h,0,sizeof h);
for (int i=1;i<=n;++i) inv[i].clear();
for (int i=1;i<=m;++i) {
int u=read(),v=read(),w=read();
add(u,v,w);
if (w) inv[v].push_back(u);
}
int ans=maxflow(1,n);
printf("%d\n",ans);
}
return 0;
}