傳送門:
網絡流(一)基礎知識篇
網絡流(二)最大流的增廣路算法
網絡流(三)最大流最小割定理
網絡流(四)dinic算法
網絡流(五)有上下限的最大流
網絡流(六)最小費用最大流問題
轉載:https://www.cnblogs.com/ZJUT-jiangnan/p/3632525.html
網絡流的相關定義:
- 源點:有n個點,有m條有向邊,有一個點很特殊,只出不進,叫做源點。
- 匯點:另一個點也很特殊,只進不出,叫做匯點。
- 容量和流量:每條有向邊上有兩個量,容量和流量,從i到j的容量通常用c[i,j]表示,流量則通常是f[i,j].
通常可以把這些邊想象成道路,流量就是這條道路的車流量,容量就是道路可承受的最大的車流量。很顯然的,流量<=容量。而對於每個不是源點和匯點的點來說,可以類比的想象成沒有存儲功能的貨物的中轉站,所有“進入”他們的流量和等於所有從他本身“出去”的流量。
- 最大流:把源點比作工廠的話,問題就是求從工廠最大可以發出多少貨物,是不至於超過道路的容量限制,也就是,最大流。
求解思路:
首先,假如所有邊上的流量都沒有超過容量(不大於容量),那么就把這一組流量,或者說,這個流,稱為一個可行流。
一個最簡單的例子就是,零流,即所有的流量都是0的流。
- (1).我們就從這個零流開始考慮,假如有這么一條路,這條路從源點開始一直一段一段的連到了匯點,並且,這條路上的每一段都滿足流量<容量,注意,是嚴格的<,而不是<=。
- (2).那么,我們一定能找到這條路上的每一段的(容量-流量)的值當中的最小值delta。我們把這條路上每一段的流量都加上這個delta,一定可以保證這個流依然是可行流,這是顯然的。
- (3).這樣我們就得到了一個更大的流,他的流量是之前的流量+delta,而這條路就叫做增廣路。我們不斷地從起點開始尋找增廣路,每次都對其進行增廣,直到源點和匯點不連通,也就是找不到增廣路為止。
- (4).當找不到增廣路的時候,當前的流量就是最大流,這個結論非常重要。
補充:
- (1).尋找增廣路的時候我們可以簡單的從源點開始做BFS,並不斷修改這條路上的delta 量,直到找到源點或者找不到增廣路。
- (2).在程序實現的時候,我們通常只是用一個c 數組來記錄容量,而不記錄流量,當流量+delta 的時候,我們可以通過容量-delta 來實現,以方便程序的實現。
相關問題:
為什么要增加反向邊?
在做增廣路時可能會阻塞后面的增廣路,或者說,做增廣路本來是有個順序才能找完最大流的。
但我們是任意找的,為了修正,就每次將流量加在了反向弧上,讓后面的流能夠進行自我調整。
舉例:
比如說下面這個網絡流模型
我們第一次找到了1-2-3-4這條增廣路,這條路上的delta值顯然是1。
於是我們修改后得到了下面這個流。(圖中的數字是容量)
這時候(1,2)和(3,4)邊上的流量都等於容量了,我們再也找不到其他的增廣路了,當前的流量是1。
但是,
這個答案明顯不是最大流,因為我們可以同時走1-2-4和1-3-4,這樣可以得到流量為2的流。
那么我們剛剛的算法問題在哪里呢?
問題就在於我們沒有給程序一個“后悔”的機會,應該有一個不走(2-3-4)而改走(2-4)的機制。
那么如何解決這個問題呢?
我們利用一個叫做反向邊的概念來解決這個問題。即每條邊(i,j)都有一條反向邊(j,i),反向邊也同樣有它的容量。
我們直接來看它是如何解決的:
在第一次找到增廣路之后,在把路上每一段的容量減少delta的同時,也把每一段上的反方向的容量增加delta。
c[x,y]-=delta;
c[y,x]+=delta;
我們來看剛才的例子,在找到1-2-3-4這條增廣路之后,把容量修改成如下:這時再找增廣路的時候,就會找到1-3-2-4這條可增廣量,即delta值為1的可增廣路。將這條路增廣之后,得到了最大流2。
那么,這么做為什么會是對的呢?
事實上,當我們第二次的增廣路走3-2這條反向邊的時候,就相當於把2-3這條正向邊已經是用了的流量給“退”了回去,不走2-3這條路,而改走從2點出發的其他的路也就是2-4。
如果這里沒有2-4怎么辦?
這時假如沒有2-4這條路的話,最終這條增廣路也不會存在,因為他根本不能走到匯點
同時本來在3-4上的流量由1-3-4這條路來“接管”。而最終2-3這條路正向流量1,反向流量1,等於沒有流。
下面是劉汝佳的模板,用水流會更容易理解
1 struct edge 2 { 3 int from, to, cap, flow;//分別是起點,終點,容量,流量 4 edge(int u, int v, int c, int f):from(u), to(v), cap(c), flow(f){} 5 }; 6 int n, m;//n為點數,m為邊數 7 vector<edge>e;//保存所有邊的信息 8 vector<int>G[maxn];//鄰接表,G[i][j]保存節點i的第j條邊在e數組里面的編號 9 int a[maxn];//每個點目前流經的水量 10 int p[maxn];//p[i]從原點s到終點t的節點i的前一條邊的編號 11 12 void init(int n) 13 { 14 for(int i = 0; i <= n; i++)G[i].clear(); 15 e.clear(); 16 } 17 void addedge(int u, int v, int c) 18 { 19 e.push_back(edge(u, v, c, 0));//正向邊 20 e.push_back(edge(v, u, 0, 0));//反向邊,容量為0 21 m = e.size(); 22 G[u].push_back(m - 2); 23 G[v].push_back(m - 1); 24 } 25 int Maxflow(int s, int t)//起點為s,終點為t 26 { 27 int flow = 0; 28 for(;;) 29 { 30 memset(a, 0, sizeof(a));//從原點s開始放水,最初每個點的水量都為0 31 queue<int>Q;//BFS拓展隊列 32 Q.push(s); 33 a[s] = INF;//原點的水設置成INF 34 while(!Q.empty()) 35 { 36 int x = Q.front();//取出目前水流到的節點 37 Q.pop(); 38 for(int i = 0; i < G[x].size(); i++)//所有鄰接節點 39 { 40 edge& now = e[G[x][i]]; 41 if(!a[now.to] && now.cap > now.flow) 42 //a[i]為0表示i點還未流到 43 //now.cap > now.flow 說明這條路還沒流滿 44 //同時滿足這兩個條件,水流可以流過這條路 45 { 46 p[now.to] = G[x][i];//反向記錄路徑 47 a[now.to] = min(a[x], now.cap - now.flow); 48 //流到下一點的水量為上一點的水量或者路徑上還可以流的最大流量,這兩者取最小值 49 Q.push(now.to);//將下一個節點入隊列 50 } 51 } 52 if(a[t])break;//如果已經流到了終點t,退出本次找增廣路 53 } 54 if(!a[t])break;//如果所有路都已經試過,水不能流到終點,說明已經沒有增廣路,已經是最大流 55 for(int u = t; u != s; u = e[p[u]].from)//反向記錄路徑 56 { 57 e[p[u]].flow += a[t];//路徑上所有正向邊的流量增加流到終點的流量 58 e[p[u]^1].flow -= a[t];//路徑上所有反向邊的流量減少流到終點的流量 59 } 60 flow += a[t];//最大流加上本次流到終點的流量 61 } 62 return flow; 63 }