引入
最大流算法分为两类,一种是增广路算法,一种是预留推进算法。增广路算法包括时间复杂度(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
",ans);
}
return 0;
}