網絡流之最大流
網絡流
題記:網絡流是最近講過的最迷算法……
網絡流(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; }
網絡流之最大流