网络流之最大流
网络流之最大流
一、 问题引入。
有n个排水口,不同的排水口之间有m条水管连接,水一开始从源点s流出,最终到达t。每条边(水管)都有一个最大的流量。除了s,t外,每个排水口的流入量都要等于流出量,询问最多能有多少水到达终点t ?
如图所示:
(即poj 1273)
可以将问题进行如下整理:
(1)用c[e]表示每条边最大的可能流量
(2)每条边对应的实际流量为f[e]
(3)根据条件,可知所有的f[e]<=c[e]
(4)目标是最大化从s发出的水流量
二、问题分析:
很容易会想到贪心算法,就是不断地从源节点s出发寻找能到达t的路径,能流就流,一路上对c[e]取min,流了之后将路上的c[e]减掉此次的流量mina。直到找不到路径为止。
但是,其实这样的算法是不正确的。
以下是《挑战程序竞赛》中的反例。
左图为贪心算法运行出的结果,答案为10,但是右图答案为11。不妨找找这两图流量的差来分析原因。
通过分析反例,可以发现,我们在流过某一条边之后,可能会导致以后无法经过此边。如左图,s->1->2->t使得s->2->t无法走,而且还限定了s->1->3->t的流量。因此,一开始就流s->1->2->t很可能不是最优的。
因此,我们需要给每条边一个“反悔”的机会,要考虑到实际上某次不流这条边的情况。
在此基础上,增广路算法横空出世。下文介绍其中的一种——Ford-fulkerson算法。
二、 Ford-fulkerson算法
我们在原先算法的基础上,将算法进行如下改进:
我们给每条边增加一条反向边,初始上限流量为0。
如前文的贪心算法那样,不断寻找s到t的路径。某一条边为e,反向边为g 。每次找到一条可行路径(流量为 res),则ans+=res,并将路径上边的上限流量减去res, 其相对的反向边的上限流量加上res。直到找不到一条流量大于0的路径,则停止算法。
将这一算法应用为样例中:
很显然,答案正确。
为何?增加反向边的意义,其实就是给了每条边一个反悔的机会。比如说第一次流过了边e,又流了一次其反向边g,则边的上限流量就又回到了初始状态,相当于没有流。每条边都可以流,也可以反悔,就保证了算法的正确性。
三、 复杂度分析
设最大流流量为F,则最坏的时间复杂度为O(F|E|)
不过实际应用中,还是很快的。
四、 代码实现
Poj1273模板题源代码:
#include<iostream> #include<cstdio> #include<cstring> using namespace std; const int MAXN=205,INF=1e7+5; int m,n,cur,s,t,a,b,va; int head[MAXN],v[MAXN]; long long ans; struct wyy { int to,va,next,type; }edge[2*MAXN]; void add(int from,int to,int va,int type) { cur++; edge[cur].to=to; edge[cur].va=va; edge[cur].next=head[from]; edge[cur].type=type; head[from]=cur; } int dfs(int now,int mina) { if(now==t) return mina; v[now]=1; int h=head[now]; while(h!=-1) { int to=edge[h].to,va=edge[h].va,ty=edge[h].type; if(v[to]==0&&va!=0) { if(int res=dfs(to,min(mina,va))) { edge[h].va-=res; if(ty==0) edge[h+1].va+=res;//求相对反向边的下标 else edge[h-1].va+=res;//实际上就是,在链表中的下标为奇数就加1,为偶数就减1。可用异或1来解决(前提是链表的下标从0开始) return res; } } h=edge[h].next; } return 0; } int main() { ios::sync_with_stdio(false); while(cin>>m>>n) { s=1,t=n; cur=-1; ans=0; memset(head,-1,sizeof(head)); for(int i=1;i<=m;i++) { cin>>a>>b>>va; add(a,b,va,0);//存边的类型,0为正向边,1为反向边。其实也可以不用记这个参数。圆桌问题中就换了一种写法。 add(b,a,0,1); } while(1) { memset(v,0,sizeof(v)); v[s]=1; int res=dfs(s,INF); if(res==0) break; ans+=res; } cout<<ans<<endl; } return 0; }