1. 程式人生 > >網絡流之最大流

網絡流之最大流

可能 pac 深度優先 bool 技術分享 模擬 機會 r+ ont

網絡流

題記:網絡流是最近講過的最迷算法……

網絡流(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;
}

  

網絡流之最大流