以前網絡流什麼的快忘完了
老師講課的時候一臉懵逼……開始系統複習,從最大流開始
標籤:網絡流-最大流算法
首先複習了網絡流的概念——
網絡流是一個有向圖,每一條邊有一個流量限制(也能夠叫作邊權),圖上有且僅有兩個特殊點:源點-入度爲0、匯點-出度爲0。除此以外的全部點都有出度和入度。
網絡流相似於「水流」,源點就至關於「無窮的水源」,從源點出發向其相鄰邊「流水」,邊上「水」的流量不能超過其流量限制(容量限制),且對於每一個點(除源點、匯點),流入該點的「水量」等於該點流出的「水量」(流守恆性)。
其次網絡流具備斜對稱性,即對於一條邊,若它的流量爲f,則它的反向邊流量爲 -f。數組
(下面簡寫源點爲Begin,匯點爲End,網絡的邊集爲 E、點集爲 V)
用式子的方法總結一下網絡流所具備的性質:
定義\(f(u,v)\)表示u到v的邊的流量,\(c(u,v)\)表示u到v的邊的流量限制(容量);只對相鄰的 u,v 有此定義。網絡
定義網絡流的 流f 爲:\(f=\sum_v f(Begin,v)=\sum_w f(w,End)\)
定義u到v的邊的殘留容量r(u,v)爲:\(r(u,v)=c(u,v)-f(u,v)\)學習
最大流即流網絡中的最大流值。
EK算法是對Ford-Fulkerson方法的實現。ui
若是 \(r(u,v)>0\),則邊 (u,v) 在殘留網絡中。
增廣路是殘留網絡中從 Begin 到 End 的一條路徑 P,\(\delta(P)=\min\{r(u,v)\},(u,v)\in P\) 表示增廣路P的殘留容量。spa
Ford-Fulkerson方法的主要思想是先構造殘餘, DFS 找到一條增廣路,而後找到增廣路上的流量 \(\delta\),將增廣路上的每一條邊的流量限制都減去 \(\delta\)、每一條邊都反向邊都流量限制都加上 \(\delta\) 。
爲何要把反向邊的流量限制加上 \(\delta\) 呢?咱們來看一個簡單的例子:
若是咱們一開始選擇流「1-2-3-4」,那麼咱們獲得流的大小爲1,可是顯然咱們能夠選擇流「1-2-4 , 1-3-4」,流的大小爲2。
若是先流「1-2-3-4」,那麼咱們就至關於肯定了邊 (2,3) 必須流,可是這是不必定的。咱們給它的反向邊 (3,2) 加上 \(\delta\)(最初全部邊的反向邊的流量限制都爲0),那麼下一次咱們流過它的反向邊 (3,2) 時,就至關於「撤銷」了流過 (2,3)。而後兩次的流量之和就是最大流。
爲何這樣是正確的?
讓咱們手推一下這張圖的最大流過程:先找到了「1-2-3-4」,而後 (1,2)(2,3)(3,4) 的流量限制減去10,(2,1)(3,2)(4,3) 加上10;再流過 「1-3-2-4」。就至關於把原來「1-2-3-4」的(2,3)斷開,接上(2,4)造成「1-2-4」;而斷開事後的「2-3-4」中,流過反向邊(3,2)使得(2,3)的流被撤銷,再接上(1,3)就造成「1-3-4」。code
一種比較基礎的對Ford-Fulkerson方法的實現,基於BFS。
首先定義一些數組,方便後面闡述:blog
(1) vis[u] 表示點 u 在該次BFS中是否被訪問過
(2) flw[u] 表示該次BFS中從源點開始流到點 u 的流最大的值(也就是源點BFS到u的路徑上的邊的最小的流量限制)
(3) preedg[u] 表示該次BFS中是從哪一條邊流到u的隊列
從源點開始,BFS遍歷整個流網絡,要求通過的有向邊的流量限制嚴格大於0,而且不重複通過同一個點。當遍歷到匯點時,返回當前流值。
假設如今要從u流到v,先要保證 vis[v] == 0 而且 (u,v) 的流量限制大於0。而後標記 vis[v] ,再記錄 preedg[v] 爲當前邊的編號;flw[v] 的計算相似於 dp,由於流值最大不超過 (u,v) 的限制,那麼遞推式也很是顯然 \(flw[v]=\min\{flw[u],c(u,v)\}\)。
若是v就是匯點,則返回 flw[v],不然將v壓入隊列。
若是最後沒法到達匯點,則返回0,表示無增廣路。get
先BFS判斷當前是否有增廣路,若是有,則BFS返回值則爲增廣路的流量 \(\delta\),則從v沿着增廣路倒過來回到源點,並將增廣路上的邊的流量限制減去 \(\delta\),增廣路上的邊的反向邊的流量限制加上 \(\delta\) 。直到沒有增廣路(BFS返回值爲0),退出循環。
其實就是把 Ford-Fulkerson 方法模擬了一遍。
所以EK算法的時間複雜度並不理想,但畢竟它是(彷佛是)最大流算法中最爲穩定的算法,有其存在的價值。
以〔洛谷 P2740〕爲原題的代碼~
/*Lucky_Glass*/ #include<cstdio> #include<cstring> #include<algorithm> #include<queue> using namespace std; const int N=200; struct FLOWGRAPH{ struct NODE{ int to,nxt,lim,rev; NODE(){} NODE(int _to,int _nxt,int _lim,int _rev): to(_to),nxt(_nxt),lim(_lim),rev(_rev){} }nod[N*2+7]; int cnt,adj[N+7]; void Rebuild(){ memset(adj,-1,sizeof adj); cnt=0; } void AddEdge(int u,int v,int lim){ int A=++cnt,B=++cnt; nod[A]=NODE(v,adj[u],lim,B),adj[u]=A; nod[B]=NODE(u,adj[v],0,A),adj[v]=B; } }grp; int m,n; int preedg[N+7],flw[N+7],vis[N+7]; int BFS(int cas){ queue<int> que; que.push(1); flw[1]=(1<<30); while(!que.empty()){ int u=que.front();que.pop(); for(int i=grp.adj[u];i!=-1;i=grp.nod[i].nxt){ int v=grp.nod[i].to; if(!grp.nod[i].lim || vis[v]==cas) continue; vis[v]=cas; preedg[v]=i;flw[v]=min(grp.nod[i].lim,flw[u]); if(v==n) return flw[v]; que.push(v); } } return 0; } int EK(){ int del,res=0,cas=0; while(del=BFS(++cas)){ int pnt=n; while(pnt!=1){ grp.nod[preedg[pnt]].lim-=del; grp.nod[grp.nod[preedg[pnt]].rev].lim+=del; pnt=grp.nod[grp.nod[preedg[pnt]].rev].to; } res+=del; } return res; } int main(){ grp.Rebuild(); scanf("%d%d",&m,&n); for(int i=0;i<m;i++){ int u,v,lim; scanf("%d%d%d",&u,&v,&lim); grp.AddEdge(u,v,lim); } int res=EK(); printf("%d\n",res); return 0; }
若是有沒看懂或者有問題的,請諮詢做者郵箱\(lucky\_glass@foxmail.com\)~