网络流之最大流
一、 问题引入。
有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;
}