最大流 學習筆記

2022-05-31 04:12:08 字數 3357 閱讀 6555

這裡總結一下幾種最大流演算法

ek演算法應該是最大流中最簡單的了,但剛開始理解也花了不少工夫

ek演算法基於增廣路。它的思想是,每一次通過bfs不停尋找增廣路,找到以後增廣,直到找不到為止

殘量

一條邊的殘量等於該邊的容量減去該邊當前的流量,為了方便,我們可以建立殘量網路。這樣每條邊只需要對應乙個權值即可。

反向邊

給程式乙個反悔的機會。因為乙個點出發邊的搜尋順序完全取決於讀入的順序,所以難免會運氣不好走出一條路使下一次無法增廣,而實際上如果不那麼走是有增廣路的。因此引入反向邊的概念,當第一次運氣不好走過一條邊時,下一次可以反的走回來,這樣一條邊走去走來就抵消了,相當於沒走,也就是反悔了

問題的統一

從u到v流去3,再從v到u流回5,實際上等價於v到u流回-2。因此,流量$f(u,v) = k$等價於$f(v,u) = -k$

具體的演算法流程及內容相關:

1.建圖

對於每一條邊,建立它本身與反向邊。

它本身的容量就設為c,流量初始化為0。 反向邊的容量設為0,流量也為0

每條邊在殘量網路中對應正反兩條邊,當容量為c,當前流量為f時,正向邊殘量為$c-f$,反向邊殘量為$0-(-f)=f$,容易看出正反向邊始終滿足殘量之和等於容量

假設當前一條邊經過增廣使流量增加了$δf$,則正向邊殘量為$c-(f+δf)$,反向邊殘量應當為$f+δf = 0-(-f-δf)$才能滿足容量的守恆。由此可知,當流量增加$δf$時,反向邊的流量應當減去$δf$

2.增廣

利用bfs進行增廣

基於已經構建好的殘量網路(也就是容量-流量),bfs出任意一條路勁,使其能夠增廣。這裡的增廣就好像乙個瓶頸,能增廣多少取決於殘量最小的那條邊。

因此我們可以記錄乙個陣列a,a[i]表示從i到源點經過的殘量的最小值。如果$a[t]>0$,則意味著存在一條增廣路

我們不停進行bfs,直到$a[t]==0$。注意a[s]應當為inf,用來打擂最小值

3.累積

注意我們不能再bfs增廣的過程中對邊的流量進行修改,因為bfs幾乎會經過所有的邊,而只有一條路徑作為增廣路。所以我們需要像最短路輸出方案那樣記錄更新的pre陣列,最後由匯點t往前一一處理經過的邊(正向加反向減)即可。這裡還有乙個小技巧:我們可以讓num_edge從0開始累積,由於正反向的兩條邊是連續add的,所以邊的編號剛好是x與x^1. 這樣訪問起來也非常方便

注意,這不是單純地輸出路徑,而是需要對路徑上的邊權做出修改。無疑不能僅僅存點的編號為pre,而是邊的編號,並且還需要在邊的屬性中加入from這一項

ek演算法複雜度最壞為$o(nm^2)$

inline void

maxflow_ek()

}if(a[t] > 0) break; //

意味著已經找到增廣路,可以停止bfs

}

if(!a[t]) break;//

無法找到增廣路了,跳出大的迴圈

for(int i = t; i != s; i = from

[pre[i]])

ans += a[t];//

每一次增廣累積答案

} printf("%d

", ans);

}

在討論dinic演算法之前,先來看最短增廣路演算法。dinic基於該演算法,事實上dinic是連續最短增廣路演算法

最短增廣路演算法的步驟如下:

1. 構建層次圖

先對原圖從源點開始bfs,處理出每個點到s的距離(這裡每條邊的權值當做1,也就可以理解為每個點到s最少經過幾條邊)。這個距離記為這個點的層次。

2. bfs求解最大流

將同層次的點歸於一組,並規定每一次只能走層次較大的(最多只能大1,想一想,為什麼)。每一次bfs的過程中找到增廣路,並進行增廣

3. 重新構建層次圖

當找不到增廣路後,去除所有飽和的邊,重新構建層次圖,重新bfs找增廣路

為什麼這樣會優呢?因為每一次構建層次圖,都是讓源點到匯點的增廣路同時為最短路。每一次重構層次圖意味著增廣路的最短路至少增加了1. 因此重構不會超過$n-1$次

再回過頭來看dinic,dinic演算法的優化就在於不要用bfs每次求解一條增廣路,這樣非常慢。dinic演算法的思想是多路增廣,即利用dfs一次性求解多條增廣路

下面來介紹dinic演算法的步驟

1. 構建層次圖

與最短增廣路演算法一樣

2. dfs多路增廣

每一次在dfs的時候就可以順帶減去當前邊的流量,因為是深度優先,所以在回溯的時候已經知道了流量的消耗。具體原理還是和ek一樣的

3. 重新構圖進行多路增廣

也和最短增廣路演算法一樣

以上是樸素的dinic演算法,我們可以嘗試對他進行優化

優化(一):當前弧優化

所謂當前弧優化就是指在同一次dfs中,記錄每個點已經訪問過的邊,下次從沒有訪問的邊開始訪問

這有點像記憶化搜尋,由於是深度優先搜尋,所以如果一條邊已經搜尋過了,那麼從它出發的經過它的所有增廣路肯定都已經增廣了(不然不會回溯到現在),所以這條邊再dfs下去毫無價值,因此可以忽略。所以反應到程式裡就是每乙個點搜過的邊都不要再搜,也就是當前弧優化

有乙個地方是不是矛盾了?既然是深度優先搜尋,那麼乙個點搜過了所有邊肯定也會被遍歷過,為什麼還需要當前弧優化呢?因為我們有乙個重要的跳出語句(也就是下一條優化):我們是有剩餘流量的,引數a表示這個點出發總共還有多少從前面運輸過來的流量,如果增廣前三條邊導致剩餘流量用光,那麼只能被迫跳出。因此當前弧優化就在這裡起到了作用

優化(二):剩餘流量為0時及時跳出

既然沒有剩餘流量了,跳出即可(如上)

dinic演算法複雜度最壞$o(n^2m)$

inline bool

bfs()}}

return vis[t];//

能訪問t意味著存在至少一條增廣路

}int dfs(int u, int

a) }

return

ans;

}inline

void

dinic()

printf("%d

", ans);

}

最大流學習筆記(2)

1 基本的ford fulkerson方法。該方法的思想就是每次找到乙個增廣路 p 然後將增廣路 p 對應的流加到之前的流上得到新的流,一直這樣直到找不到增廣路,這時候找到的流就是最大流。演算法的偽 如下 假設容量是整數,最大流為 f 那麼while迴圈最多執行 f 次,因為每次至少使得流量增加1,...

最大流學習筆記(1)

1流網路。流網路g v,e 是乙個有向圖,每條邊 u,v in e 有乙個非負容量值 c u,v geq 0 如果 u,v notin e,c u,v 0 另外有乙個源節點s和匯點t。2流。g中的流是乙個實值函式 f v times v rightarrow r 滿足 1 容量限制 對所有的 u,v...

最大流學習筆記(2)

1 基本的ford fulkerson方法。該方法的思想就是每次找到乙個增廣路 p 然後將增廣路 p 對應的流加到之前的流上得到新的流,一直這樣直到找不到增廣路,這時候找到的流就是最大流。演算法的偽 如下 假設容量是整數,最大流為 f 那麼while迴圈最多執行 f 次,因為每次至少使得流量增加1,...