一開始的時候,學習網絡流只是由於想作 bzoj 1001。學習了 dinic 算法以後,順便寫了這麼一篇學習筆記,爭取可以幫到更多的同窗理解網絡流。html
所以若是對這篇文章有什麼不理解的地方,請在評論區留言,博主會盡可能修改,若是可以提出改進意見,不勝感激。算法
這些概念多是在下文中才用獲得的,所以能夠先不閱讀此部分。若有不理解的再回到目錄部分查閱,這些概念大體按照在文章中出現的順序排列。網絡
網絡流是圖論中的一類經典問題,也是一種博大精深的問題,也是 OI 中很是有意思的一種問題。學習
什麼是網絡?優化
能夠理解爲一張有向圖,記這張有向圖爲 \(G(V, E)\)spa
在這張有向圖的點集 \(V\) 中,有且只有一個源點 \(s\),一個匯點 \(t\) 和若干個中轉點。.net
在邊集 \(E\) 中,每條邊都有一個容量 \(c\)。htm
這樣的有向圖被稱爲網絡。blog
注意:網絡並非一個 DAG,也不保證 \(s\) 入度爲 \(0\) 和 \(t\) 出度爲 \(0\)。圖片
形象的說,源點就是現實中網絡上的發送站,匯點就是接收站。
發送站向接收站傳輸一些文件,傳輸的文件就是一個流。
這個流的大小稱爲流量 \(f\)。
顯然這個流必須是從源點出發,而後通過某些中轉點和邊,最後到達匯點的。
這個流量不可以超過任何一條邊的容量,所以若通過這些邊,那麼這個流的流量最大爲通過的全部邊的最小容量。
注意:若是一條邊有 \(f\) 的流量,那麼這條邊的反邊就有 \(-f\) 的流量。當一條邊的流量增長 \(f\) 的時候,其對應的反邊流量減小 \(f\)
最大流是網絡流中最簡單的一類問題,也是最適合新手入門的一類問題。
在網絡中,類型不一樣的節點對流量有不一樣的性質,其中:
在知足全部邊的容量限制的狀況下,充分利用整個網絡的節點,從源點出發,最後在匯點接受的最大流量,稱爲這個網絡的最大流。
費用流的一類經典問題是「最小費用最大流」。
對於每條邊,除了有容量限制以外,還有一個單位流量的花費。
「最小費用最大流」即在知足整張圖取到最大流的狀況下,最小化花費。
當前狀態下的流被稱爲當前流,當前流在增廣操做中會被修改,不斷增廣以後就會獲得最大流。
一個可以到達匯點的有流量的流被稱爲可行流。
對於一條邊,在已經有一部分流量的基礎上,剩餘可用的容量被稱爲殘餘容量。
對於整張圖,在已經造成若干的流以後,由節點和全部有殘餘容量的邊及其殘餘容量構成的網絡被稱爲殘量網絡。
若是在網絡中,可以再找到一條路徑,使得整個網絡依據這條路徑修改當前流後可以使得整個網絡的流量增長,那麼這條路被稱爲增廣路,按照增廣路修改當前流的操做被稱爲增廣。
所以,顯然當前流是最大流的充要條件是在當前流下不存在任何一條增廣路。
特別的,增廣路在殘量網絡上的體現即爲在殘量網絡上,從源點可以找到一條到達匯點的路徑。
因此,當前流是最大流的充要條件等價於在殘量網絡上,源點和匯點不連通。
一種有效給搜索節點的順序進行排布的方式。保證只搜下一層節點能夠實如今增廣以後不會再影響反邊。被分層處理以後的圖被稱爲分層圖。
給一個節點打上標識,使得接下來的搜索過程當中沒法再搜到這個節點。
在一個網絡中,選取邊集 \(E'\subseteq E\),使得斷掉這些邊以後 \(s\) 和 \(t\) 不連通,這樣的一個邊集 \(V'\) 被稱爲這個網絡的一個割。
在一個網絡中,每條邊都有一個代價 \(p\),定義一個割的代價爲 \(\sum_{(u, v)\in E'} p_{(u, v)}\),這個網絡中代價最小的割被稱爲最小割。
發現若要在原圖中找到一個可行流,很容易。
可是若是想要修改當前流,不是那麼好實現,可能須要對當前流的某些邊進行一個「反悔」。
可是,若是對於每一條初始邊 \((u, v, c)\in E\),建一條反向邊 \((v, u, 0)\),就能夠很好的解答這個問題。
用邊的權表示當前邊的殘餘容量。
那麼當在一條邊上進行增廣以後,這條邊的流量就 \(+f\), 而反邊的流量會 \(-f\)。體現爲殘餘容量,就是正向邊的殘餘容量 \(-f\) 而反向邊的殘餘容量 \(+f\)。
舉個例子表示一下:
圖中 \(s = 1, t = 4\),藍色數字表示容量,紅色線表示當前流,紅色數字表示流量。
那麼此圖中對應的殘量網絡就是:
圖中綠色表示殘餘容量,只要有殘餘容量的邊都標出了,包括以前的反邊。
注意:在原圖中造成了一個 \((1, 2, 2)\to(2, 3, 2)\to(3, 4, 2)\) 的當前流也就意味着它們邊 \((2, 1),(3, 2),(4, 3)\) 的殘餘容量分別增長了 \(2\)。
根據在殘量網絡上 \(s\) 和 \(t\) 仍然連通,得出在當前流下,仍然存在增廣路。
爲了演示修改當前流的「反悔」過程,這裏先不考慮最優性,單純地參考當前的殘量網絡,按照 \((1, 3)\to(3, 2)\to(2, 4)\) 這樣一條可行路徑進行增廣,增長的流量爲 \(2\),那麼,此次增廣以後殘量網絡就變成了:
這樣一次操做以後對應的當前流變成了什麼樣呢?這個也不難想象。
能夠發現,在這樣一次增廣過程當中,由於通過了 \((2, 3)\) 這條邊的反邊,因此這條邊上原來造成的流就被天然的「反悔」了。
而本來的流量,沒有受到任何影響,這樣和直接從 \((1, 2)\to(2, 4)\) 和 \((1, 3)\to(3, 4)\) 進行增廣沒有任何區別。
所以,只要不斷在殘量網絡上進行增廣,就能夠代替在原圖修改當前流的反悔過程。
通過剛纔的分析,咱們就獲得了一種很是簡單且易理解的暴力算法,稱爲 Ford-Fulkerson 算法。
具體實現就是:
在殘量網絡上找到一條增廣路。
對當前流進行增廣。
重複 1, 2 過程直到 \(s, t\) 不連通爲止,這時就找到了最大流。
Ford-Fulkerson 算法易與理解,可是複雜度爆炸。咱們須要更高效的算法。
簡單求解網絡最大流通常採用 dinic 算法。
dinic 算法的基本思想是:按照殘量網絡經過 bfs 對當前的節點進行分層,在得出的分層圖上進行多路增廣,而且及時封鎖再也不會對答案產生貢獻的節點。
多路增廣的可行性?過程當中由於已經對節點進行分層,只搜索下一層的節點就不會對以前的操做產生任何影響。
多路增廣和封鎖無用節點是 dinic 效率高的重要緣由。
實現過程:
經過 bfs 在殘量網絡上最節點進行分層,同時判斷圖是否連通。是,進行增廣;否,直接返回,已經找到最大流。
經過 dfs 在分層以後的圖上進行增廣。每次搜索的時候強制讓其只能抵達下一層的節點。若是搜到匯點,就返回當前有的流量;不然則繼續往深處搜,若是回溯回來以後發現沒有可行流,那麼就說明在殘量網絡上達不到匯點,這時把這個點封鎖便可。
重複 1, 2 過程。
扔張圖理解一下,挺形象的。
(圖片來源:https://www.luogu.com.cn/blog/cicos/Dinic,侵權刪)
dinic 的時間複雜度爲 \(\mathcal O(n^2m)\),且大多時候跑不滿,在通常處理問題中已經足夠優秀。
dinic 在實際運行中的效率已經足夠優秀,並且它還能夠優化,這就是當前弧優化。
當前弧優化的原理是:由於 dinic 實現的是多路增廣,那麼能夠簡單理解爲以前枚舉過的出邊都已有效增廣,所以當一個點被枚舉到的時候,只須要從以前枚舉完的出邊繼續枚舉就能夠了。
這樣,雖然漸進時間複雜度上界沒有改變,可是實際上運行效率能夠提高很多。
證實:扔個link。
轉化一下,很容易發現,一個二分圖的最大匹配,就等同與分別用源點和匯點鏈接左右子圖,而後跑最大流。
最大流最小割定理:bzoj1001 [ICPC - Beijing2006] 狼抓兔子
二分圖最大匹配:【網絡流 24 題】飛行員配對方案
HLPP 算法,比 dinic 效率更高。
有意的讀者能夠自行探究,這超出瞭如今的討論範圍。
對比 Ford-Fulkerson 實現的方式,使用 bfs 進行遍歷找到最小的到達匯點的步數,統計路徑及路徑上的最小邊權,而後進行增廣便可。
這是 Edmonds-Karp 實現最大流的方式。
而費用流,則是將 bfs 改成 spfa 尋找最短路。
dijkstra 當然也是能夠的,加上奇怪的處理負權邊的方式就行了。
待填。