网络流之最大流
Posted yiyiyizqy
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了网络流之最大流相关的知识,希望对你有一定的参考价值。
网络流
题记:网络流是最近讲过的最迷算法……
网络流(network-flows)是一种类比水流的解决问题方法,与线性规划密切相关。非常重视选手在网络流上的建模技巧,画图是非常关键的。
1、最大流
问题引入:
有n条沟渠,与水坑s、t相连,汇聚成m个点,第i条沟渠的水流的流量为c[i],每一个点的流入量和流出量都要相等,水由原点水坑s,汇聚到水坑t,求可以有多少水可以汇聚过去。
(poj. 1273)
如图所示:
可以将问题进行如下整理:
(1)用c[e]表示每条边最大的可能流量
(2) 每条边对应的实际流量为f[e]
(3) 根据条件,可知所有的f[e]<=c[e]
(4) 目标是最大化从s发出的水流量
问题分析:
很容易会想到贪心算法,但很明显,贪心不一定是最优的,如果从某一个地方,流过全部的水,那么从其他地方便不能再用这条沟渠了。
图一
图二
举个例子:如图1为某个数据用贪心算法所得的值,由某一条沟渠开始流过,尽可能地多流,最后得出最多的水为10。
但最优解应该是图2,正确答案为11。那么是哪里多了了呢?
不防用正解与贪心做法流过水的差值做一个对比,通过对流量的差可以发现,我们通过将原先得到的流给推回去(图中的-1部分),而得到了新的流,从而达到最优解。
为什么贪心得不到最优解:
当贪心得到一定的值的时候,那么这条沟渠便不在能用,把很多原来可能是可以流过去的水给卡在那里,而汇聚不到t点,用wyy的说法来说,可能会“挡路”。
在这里大概讲一下,画几个样例便会明白。
最大流的定义:
我们称使得传输量最大的f为最大流,而求解最大流的问题称为最大流问题。而我们学的是增广路算法。
关于反向边的建立:
如上面说到的正解,是通过把流推回去而得到的,这么说明,需要有一个反悔的机会,即不选择流过原来那么多水,这下反向边就很关键了。
图中曲线为建立的反向边,直线为正向边。当水流过正向边的时候,正向边还可以流过的水量自然要减去已经流过的f,而同时也可以返回f的流水,也就是给反向边的边权变为f。
这样的好处是,下一次再扫路径发现会有更优时,可以倒退回去,当然,不管是走反向边还是走正向边,都要把另一条边加上流过的水量,以便与以后反悔。
增广路的定义(注意不是针管路哦):
我们所考虑的f[e]<c[e]的e和满足f[e]>0的e对应的反向边所组成的图称为残余网络,并称残余网络上s->t的路径为增广路。
Ford和Fulkerson迭加算法:
便是在贪心算法的基础上的迭代。
把各条弧上单位流量的费用看成某种长度,用求解最短路问题的方法确定一条自V1至Vn的最短路;在将这条最短路作为可扩充路,用求解最大流问题的方法将其上的流量增至最大可能值;
而这条最短路上的流量增加后,其上各条弧的单位流量的费用要重新确定,如此多次迭代,最终得到最大流。
具体实现:
我采用的是数组模拟指针的方法,毕竟指针不怎么会用,而用vertor怕不能准确地找到它的反向边。
数组模拟指针因为是按顺序储存,把正向边标为0,反向边标为1,先存正向边,再存反向边。
“无限”跑dfs,直到找不到一条能由s=1到t=n的路径。每一次在路径上取最小的流量(mi),这样可以确保每一段路减去mi不会为负数。同时要更改此时还可以流过的流量(va),因为已经流过了这么多水,要用原来的va-mi,同时要把对面的边(正边找反边,反边找正边)加上这么多(可后悔的值)。
这里顺便说一下怎么找对面的边:要找当前的反向边时,只用加一即可,如果找正向边,就减一。这样会好理解一点,因为他们是按顺序储存的!!也可以用异或,代码更简洁。
时间复杂度:
最多进行深度为F次深度优先搜索,所以其复杂度为O(F|E|),最坏的情况基本上不存在。所以在多数情况下,及时得出的复杂度偏高,实际运用中还是比较快的。
#include<iostream> #include<cstdio> #include<cstring> #include<vector> using namespace std; const int maxn=205,oo=10000005; int n,m,a,b,c,s,t,ans; int cur=-1,head[maxn]; bool v[maxn]; struct water { 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].type=type; edge[cur].next=head[from]; head[from]=cur; } int dfs(int now,int mi) { if(now==t) return mi; v[now]=true; int h=head[now]; while(h!=-1) { int to=edge[h].to; int va=edge[h].va; if(v[to]==false&&va!=0) { int k=dfs(to,min(va,mi)); if(k!=0) { edge[h].va-=k; if(edge[h].type==0) edge[h+1].va+=k; else edge[h-1].va+=k; return k; } } h=edge[h].next; } return 0; } int main() { while(cin>>m>>n) { ans=0; cur=-1; memset(head,-1,sizeof(head)); s=1,t=n; for(int i=1;i<=m;i++) { cin>>a>>b>>c; add(a,b,c,0); add(b,a,0,1); } while(1) { int res; memset(v,false,sizeof(v)); res=dfs(s,oo); if(res==0) break; ans+=res; } cout<<ans<<endl; } return 0; }
以上是关于网络流之最大流的主要内容,如果未能解决你的问题,请参考以下文章