网络流之最大流

网络流

题记:网络流是最近讲过的最迷算法……

    网络流(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;
}

  

posted @ 2017-08-19 15:57  yiyiyizqy  阅读(241)  评论(0编辑  收藏  举报