网络流初探
網絡流初探
2019.7.15,這個蒟蒻終于聽懂了一聽就很高大上的網絡流其實是這個人過于zz,于是她準備來水一發博客。
前言
網絡流解決的主要是這樣一類問題:
給定一個源點、一個匯點,并給定\(n\)個點,\(m\)條邊,每條邊有一個最大流量,現從源點源源不斷地注入可以視為無窮大的水流,問在匯點能接受的最大水流是多少?
網絡流是類比水流的一種解決問題的方法,故下文為講解方便,會經常使用水流來作類比。
正文
由于kma太菜了根本不會最高標號預流推進最高標號預流推進對于解決網絡流問題速度的提升并不明顯(雖然在某些特定的圖上跑得挺快),故這里僅討論增廣路算法。
增廣路
增廣路:當從\(S\)開始流過一條路徑并到達\(T\),使得當前的最大流量能夠增加,則這條路徑稱為一條增廣路。
在增廣路算法中,求最大流的問題就變成了不斷求解增廣路的問題,顯然當圖中已經不存在一條增廣路時,當前的答案就是我們要求得的最大流。
那么具體怎么操作呢?
其實很簡單,只需要從\(S\)點開始\(bfs\),每次通過權值大于0的邊,直到找到\(T\)為止,這個時候你就找到了一條增廣路。那么找到這條增廣路上權值最小的邊,將邊權記為\(gmin\),然后讓\(max \_ flow\)加上\(gmin\),增廣路上每條邊的權值減去\(gmin\)。反復進行上述操作,直到找不到增廣路即可。
為什么要在每條邊的邊權上減去\(gmin\)呢?當我們找到一條增廣路,相當于這條邊已經流了這么多流量,剩下能流的還有多少。對于一條邊,顯然我們只關心它還能流多少流量,而不是原本能流多少流量。
然而其實這樣做有一個問題:萬一流錯了怎么辦?
有一種東西叫反向邊。
反向邊
反向邊就是拿給你反悔用的。
具體來說,我們在原圖的每條邊上都加一條反向邊,且初始流量為0,當我們每次流經這條邊時,我們在它的權值上減去當前流量,然后再在它的反向邊上加上當前的流量。形象來說,我們接了一根返回去的水管。當我們每流經正向的水管時,擴容反向的水管,下次如果發現流錯了,就從反向的水管流回去,這樣正負相抵,就實現了一次反悔的操作。
好了,如果你上面的東西都看懂了,你已經成功學會了EK算法。
恭喜你成功學會了一個慢得要死的算法
總結一下:EK算法的核心:數次bfs找增廣路,并通過反向邊實現撤銷操作。
當然,講這么一大堆也并不是全是廢話,畢竟接下來要說的算法本質上都是基于EK算法進行了優化。
Dinic
首先我們看看上面的算法有什么不足:
EK算法每次會遍歷整個殘量網絡,但
等等我是不是講掉了一個小知識點。
殘量網絡
殘量網絡的定義:在任意時刻,網絡中所有節點及剩余容量大于0的邊構成的子圖稱為殘量網絡。
簡單來說,就是把那些已經流滿的邊丟掉,剩下來的圖一定是原圖的一個子圖,這個圖稱為殘量網絡。
在\(EK\)算法中,每次\(bfs\)將遍歷整個殘量網絡,但僅僅只找出一條增廣路,效率較低。
分層圖
設\(d[x]\)表示節點的層次,即從\(S\)點到節點\(x\)經過的最少邊數。在\(Dinic\)算法中,我們將滿足\(d[y] = d[x] + 1\)的邊\((x, y)\)構建成一個分層圖。每次\(bfs\)構造分層圖,在分層圖中使用\(dfs\)進行增廣,一旦走到匯點,則向前回退并沿途更新邊上邊權,直到退到某個節點有另外的流量\(>0\)的出邊,再從這個節點繼續\(dfs\)下去。這樣一次\(dfs\)能實現多次增廣,當無法增廣時再重新\(bfs\)構造殘量網絡和分層圖并重復上述步驟。
總結一下:Dinic算法的幾個步驟:
好的,現在我們講完\(Dinic\)啦!OvO
ISAP
\(ISAP\)算法是在\(Dinic\)算法基礎上的進一步優化。其本身也利用分層圖的思想,但不同于\(Dinic\)的是,\(ISAP\)算法并不通過每次重新\(bfs\)來構造分層圖,而是在每次\(dfs\)增廣之后將所有已經沒有可走增廣路的點的深度更新。因為當這個點的深度可以被更新時,說明其在當前分層圖下已經沒有可行方案可以走了,于是將其重新分層,相當于動態分層的過程。
GAP優化
在執行\(ISAP\)算法的時候,如果當前出現了斷層(某一層點的個數為0),那么說明已經沒有增廣路了,退出搜索即可。
講完啦!完結撒花!??ヽ(° ▽° )ノ?
就直接上\(ISAP\)的代碼啦:
代碼:
#include <bits/stdc++.h> #define N (400000 + 5) #define M (400000 + 5) #define int long long using namespace std; inline int read() {int cnt = 0, f = 1; char c = getchar();while (!isdigit(c)) {if (c == '-') f = -f; c = getchar();}while (isdigit(c)) {cnt = (cnt << 3) + (cnt << 1) + c - '0'; c = getchar();}return cnt * f; } int tot = 1; int n, S, T; int to[N]; int flow[M]; int nxt[M]; int first[N]; int dep[N]; int cnt[N]; void Add(int x, int y, int z) {nxt[++tot] = first[x], first[x] = tot, to[tot] = y, flow[tot] = z;nxt[++tot] = first[y], first[y] = tot, to[tot] = x, flow[tot] = 0; }void bfs_(int s) {memset(dep, 0xff, sizeof(dep));dep[s] = 0;cnt[0] = 1;queue<int> q;q.push(s);while (!q.empty()) {int p = q.front();q.pop();for (register int i = first[p]; i >= 2; i = nxt[i]) {int v = to[i];if (dep[v] == -1) {++cnt[dep[v] = dep[p] + 1];q.push(v);}}} }int max_flow;int dfs_(int p, int f) {if (p == T) {max_flow += f;return f;}int u = 0;for (register int i = first[p]; i >= 2; i = nxt[i]) {int v = to[i];if (flow[i] && dep[v] == dep[p] - 1) {int uu = dfs_(v, min(flow[i], f - u));if (uu) {flow[i] -= uu;flow[i ^ 1] += uu;u += uu;}if (u == f) {return u;}}}if (!--cnt[dep[p]]) {dep[S] = n + 1;}++cnt[++dep[p]];return u; } int m; int x, y, z; signed main(){n = read(); m = read(); S = read(); T = read();for (register int i = 1; i <= m; i++) {x = read(); y = read(); z = read();Add(x, y, z);}bfs_(T);while (dep[S] < n) {dfs_(S, 0x3FFFFFF);}printf("%lld", max_flow);return 0; }轉載于:https://www.cnblogs.com/kma093/p/11193233.html
總結
- 上一篇: 上传文件、上传按钮、Form组件上传文件
- 下一篇: 阶段1 语言基础+高级_1-2 -面向对