傳送門:
網絡流(一)基礎知識篇
網絡流(二)最大流的增廣路算法
網絡流(三)最大流最小割定理
網絡流(四)dinic算法
網絡流(五)有上下限的最大流
網絡流(六)最小費用最大流問題
轉自:https://www.cnblogs.com/SYCstudio/p/7260613.html
朴素算法的低效之處
雖然說我們已經知道了增廣路的實現,但是單純地這樣選擇可能會陷入不好的境地,比如說這個經典的例子:

我們一眼可以看出最大流是999(s->v->t)+999(s->u->t),但如果程序采取了不恰當的增廣策略:s->v->u->t

我們發現中間會加一條u->v的邊

而下一次增廣時:

若選擇了s->u->v->t

然后就變成

這是個非常低效的過程,並且當圖中的999變成更大的數時,這個劣勢還會更加明顯。
怎么辦呢?
引入Dinic算法
以下轉自:https://www.cnblogs.com/SYCstudio/p/7260613.html
預備知識:
殘留網絡:設有容量網絡G(V,E)及其上的網絡流f,G關於f的殘留網絡即為G(V',E'),其中G’的頂點集V'和G的頂點集V相同,即V'=V,對於G中任何一條弧<u,v>,如果f(u,v)<c(u,v),那么在G'中有一條弧<u,v>∈E',其容量為c'(u,v)=c(u,v)-f(u,v),如果f(u,v)>0,則在G'中有一條弧<v,u>∈E',其容量為c’(v,u)=f(u,v).
從殘留網絡的定義來看,原容量網絡中的每條弧在殘留網絡中都化為一條或者兩條弧。在殘留網絡中,從源點到匯點的任意一條簡單路徑都對應一條增光路,路徑上每條弧容量的最小值即為能夠一次增廣的最大流量。

頂點的層次:在殘留網絡中,把從源點到頂點u的最短路徑長度,稱為頂點u的層次。源點 Vs的層次為0.例如下圖就是一個分層的過程。

注意:
(1)對殘留網路進行分層后,弧可能有3種可能的情況。
1、從第i層頂點指向第i+1層頂點。
2、從第i層頂點指向第i層頂點。
3、從第i層頂點指向第j層頂點(j < i)。
(2)不存在從第i層頂點指向第i+k層頂點的弧(k>=2)。
(3)並非所有的網絡都能分層。

2、最短路增廣路徑的算法思想
最短增廣路的算法思想是:每次在層次網絡中找一條含弧數最少的增廣路進行增廣。最短增廣路算法的具體步驟如下:
(1)初始化容量網絡和網絡流。
(2)構造殘留網絡和層次網絡,若匯點不在層次網絡中,則算法結束。
(3)在層次網絡中不斷用BFS增廣,直到層次網絡中沒有增廣路為止;每次增廣完畢,在層次網絡中要去掉因改進流量而導致飽和的弧。
(4)轉步驟(2)。
在最短增廣路算法中,第(2)、(3)步被循環執行,將執行(2)、(3)步的一次循環稱為一個階段。在每個階段中,首先根據殘留網絡建立層次網絡,然后不斷用BFS在層次網絡中增廣,直到出現阻塞流。注意每次增廣后,在層次網絡中要去掉因改進流量而飽和的弧。該階段的增廣完畢后,進入下一階段。這樣的不斷重復,直到匯點不在層次網絡中為止。匯點不在層次網絡中意味着在殘留網絡中不存在一條從源點到匯點的路徑,即沒有增廣路。
在程序實現的時候,並不需要真正“構造”層次網絡,只需要對每個頂點標記層次,增廣的時候,判斷邊是否滿足level(v) = level(u)+1這一約束條件即可。
3、最短增廣路算法復雜度分析
最短增廣路的復雜度包括建立層次網絡和尋找增廣路兩部分。
在最短增廣路中,最多建立n個層次網絡,每個層次網絡用BFS一次遍歷即可得到。一次BFS的復雜度為O(m),所以建層次圖的總復雜度為O(n*m)。
每增廣一次,層次網絡中必定有一條邊會被刪除。層次網絡中最多有m條邊,所以認為最多可以增廣m次。在最短增廣路算法中,用BFS來增廣,一次增廣的復雜度為O(n+m),其中O(m)為BFS的花費,O(n)為修改流量的花費。所以在每一階段尋找增廣路的復雜度為O(m*(m+n)) = O(m*m)。因此n個階段尋找增廣路的算法總復雜度為O(n*m*m)。
兩者之和為O(n*m*m)。
以上介紹最短增廣路算法只是為下面介紹Dinic算法而提供給大家一個鋪墊,有了以上的基礎,接下來我們來介紹Dinic算法,Dinic其實是最短增廣路算法的優化版。

連續最短增廣路算法----Dinic算法
1、Dinic算法思路
Dinic算法的思想也是分階段地在層次網絡中增廣。它與最短增廣路算法不同之處是:最短增廣路每個階段執行完一次BFS增廣后,要重新啟動BFS從源點Vs開始尋找另一條增廣路;而在Dinic算法中,只需一次DFS過程就可以實現多次增廣,這是Dinic算法的巧妙之處。Dinic算法具體步驟如下:
(1)初始化容量網絡和網絡流。
(2)構造殘留網絡和層次網絡,若匯點不再層次網絡中,則算法結束。
(3)在層次網絡中用一次DFS過程進行增廣,DFS執行完畢,該階段的增廣也執行完畢。
(4)轉步驟(2)。
在Dinic的算法步驟中,只有第(3)步與最短增廣路相同。在下面實例中,將會發現DFS過程將會使算法的效率有非常大的提高。
Dinic算法復雜度分析
與最短增廣路算法一樣,Dinic算法最多被分為n個階段,每個階段包括建層次網絡和尋找增廣路兩部分,其中建立層次網絡的復雜度仍是O(n*m)。
現在來分析DFS過程的總復雜度。在每一階段,將DFS分成兩部分分析。
(1)修改增廣路的流量並后退的花費。在每一階段,最多增廣m次,每次修改流量的費用為O(n)。而一次增廣后在增廣路中后退的費用也為O(n)。所以在每一階段中,修改增廣路以及后退的復雜度為O(m*n)。
(2)DFS遍歷時的前進與后退。在DFS遍歷時,如果當前路徑的最后一個頂點能夠繼續擴展,則一定是沿着第i層的頂點指向第i+1層頂點的邊向匯點前進了一步。因為增廣路經長度最長為n,所以最壞的情況下前進n步就會遇到匯點。在前進的過程中,可能會遇到沒有邊能夠沿着繼續前進的情況,這時將路徑中的最后一個點在層次圖中刪除。
注意到每后退一次必定會刪除一個頂點,所以后退的次數最多為n次。在每一階段中,后退的復雜度為O(n)。
假設在最壞的情況下,所有的點最后均被退了回來,一共共后退了n次,這也就意味着,有n次的前進被“無情”地退了回來,這n次前進操作都沒有起到“尋找增廣路”的作用。除去這n次前進和n次后退,其余的前進都對最后找到增廣路做了貢獻。增廣路最多找到m次。每次最多前進n個點。所以所有前進操作最多為n+m*n次,復雜度為O(n*m)。
於是得到,在每一階段中,DFS遍歷時前進與后退的花費為O(m*n)。
綜合以上兩點,一次DFS的復雜度為O(n*m)。因此,Dinic算法的總復雜度即O(m*n*n)。

1 struct edge 2 { 3 int u, v, c, f; 4 edge(int u, int v, int c, int f):u(u), v(v), c(c), f(f){} 5 }; 6 vector<edge>e; 7 vector<int>G[maxn]; 8 int level[maxn];//BFS分層,表示每個點的層數 9 int iter[maxn];//當前弧優化 10 int m; 11 void init(int n) 12 { 13 for(int i = 0; i <= n; i++)G[i].clear(); 14 e.clear(); 15 } 16 void addedge(int u, int v, int c) 17 { 18 e.push_back(edge(u, v, c, 0)); 19 e.push_back(edge(v, u, 0, 0)); 20 m = e.size(); 21 G[u].push_back(m - 2); 22 G[v].push_back(m - 1); 23 } 24 void BFS(int s)//預處理出level數組 25 //直接BFS到每個點 26 { 27 memset(level, -1, sizeof(level)); 28 queue<int>q; 29 level[s] = 0; 30 q.push(s); 31 while(!q.empty()) 32 { 33 int u = q.front(); 34 q.pop(); 35 for(int v = 0; v < G[u].size(); v++) 36 { 37 edge& now = e[G[u][v]]; 38 if(now.c > now.f && level[now.v] < 0) 39 { 40 level[now.v] = level[u] + 1; 41 q.push(now.v); 42 } 43 } 44 } 45 } 46 int dfs(int u, int t, int f)//DFS尋找增廣路 47 { 48 if(u == t)return f;//已經到達源點,返回流量f 49 for(int &v = iter[u]; v < G[u].size(); v++) 50 //這里用iter數組表示每個點目前的弧,這是為了防止在一次尋找增廣路的時候,對一些邊多次遍歷 51 //在每次找增廣路的時候,數組要清空 52 { 53 edge &now = e[G[u][v]]; 54 if(now.c - now.f > 0 && level[u] < level[now.v]) 55 //now.c - now.f > 0表示這條路還未滿 56 //level[u] < level[now.v]表示這條路是最短路,一定到達下一層,這就是Dinic算法的思想 57 { 58 int d = dfs(now.v, t, min(f, now.c - now.f)); 59 if(d > 0) 60 { 61 now.f += d;//正向邊流量加d 62 e[G[u][v] ^ 1].f -= d; 63 //反向邊減d,此處在存儲邊的時候兩條反向邊可以通過^操作直接找到 64 return d; 65 } 66 } 67 } 68 return 0; 69 } 70 int Maxflow(int s, int t) 71 { 72 int flow = 0; 73 for(;;) 74 { 75 BFS(s); 76 if(level[t] < 0)return flow;//殘余網絡中到達不了t,增廣路不存在 77 memset(iter, 0, sizeof(iter));//清空當前弧數組 78 int f;//記錄增廣路的可增加的流量 79 while((f = dfs(s, t, INF)) > 0) 80 { 81 flow += f; 82 } 83 } 84 return flow; 85 }
