1. 程式人生 > >網路最大流 Dinic演算法

網路最大流 Dinic演算法

# 前言 看到網上好多都用的鏈式前向星,就我在用 $vector$ 鄰接表…… # 定義 先來介紹一些相關的定義。(個人理解) ## 網路 一個網路是一張帶權的有向圖 $G=(V,E)$ ,其中每任意一條邊 $(u,v)$ 的權值稱為這條邊的容量 $c(u,v)$ 。若這條邊不存在,對應的容量就為 $0$ 。其中包含兩個特殊的點:源點 $S$ 與匯點 $T$ 。 ## 流量 $f$ 為網路的流函式,每一條邊都有對應的流量。對於合法的流函式包含以下性質。 1. 容量限制: $f(u,v)≤c(u,v)$ 2. 斜對稱: $f(u,v)=-f(v,u)$ 3. 流量守恆:對於任意滿足不為源點或匯點的節點 $k$ ,有: $∑_{u∈E}f(u,k)=∑_{v∈E}f(k,v)$ ## 最大流 對於一個網路,不難發現合法的流函式很多。這張圖的流量為 $∑_{k∈E}f(S,k)$ ,顧名思義,最大流就是這張網路的最大流量。 ## 增廣路 存在一條從 $S$ 到 $T$ 的路徑,使得路徑上所有的流量都不為 $0$ ,則稱該路徑為增廣路。 ## 殘量網路 對於任意時刻,當前時刻網路中,由所有結點與剩餘容量大於 $0$ 的邊構成的該網路的子圖被稱為殘量網路。 ## 分層圖 在這個演算法中,滿足層數 $de[v]=de[u]+1$ 的邊 $(u,v)$ ,所構成的子圖稱為分層圖。 # Dinic演算法 ## 建圖 一條邊中需要包含以下資訊:終點節點編號,邊的容量,相反的邊的編號。 ```cpp struct Node { int to, value, rev; Node() {} Node(int T, int V, LL R) { to = T;//節點編號 value = V;//邊的容量 rev = R;//相反的邊的編號 } }; ``` 得雙向存邊,給出一條邊 $(A,B)$ ,其長度為 $C$ ,建一條從 $A$ 到 $B$ 的邊,權值為 $C$ ,與之相反的邊權值為 $0$。 ```cpp for(int i = 1; i <= m; i++) { Quick_Read(A); Quick_Read(B); Quick_Read(C); int idA = v[A].size(), idB = v[B].size(); v[A].push_back(Node(B, C, idB)); v[B].push_back(Node(A, 0, idA)); } ``` 雙向存邊是為了給後面 $dfs$ 時,若存在更優解,可以使得程式反悔,重新走另一條路。這裡暫時不懂可以繼續看後面的程式碼再來理解這樣建圖的意義。 ## 主體部分 * 一遍 $bfs$ ,將殘量網路構造成分層圖,並求出當前殘量網路是否存在增廣路。 * 一遍 $dfs$ ,在該分層圖中尋找增廣路,將這條讓這條增廣路消失。 重複上述兩個操作,直到當前網路中不存在增廣路。 先來看 $bfs$ 。其返回值為 $bool$ ,意為該殘量網路中是否還存在增廣路。層數 $de[i]$ 的意義很明白: $S$ 到達當前的點 $i$ 的最小步數。而按照這樣的分層,每次只能將當前流量資訊傳遞到下一層數節點上,可以很大程度上避免張冠李戴的情況。若 $T$ 的層數為 $1$ ,則說明當前 $S$ 不能通向 $T$ ,故而不存在增廣路,跳出迴圈。 ```cpp bool bfs_Dinic() {//bfs將殘餘網路分層,返回是否圖中還存有增廣路 memset(de, 0, sizeof(de));//清空深度 while(!q.empty()) q.pop(); q.push(s); de[s] = 1; be[s] = 0; while(!q.empty()) { int now = q.front(); q.pop(); int SIZ = v[now].size(); for(int i = 0; i < SIZ; i++) { int next = v[now][i].to; if(v[now][i].value && !de[next]) { q.push(next); be[next] = 0;//分層圖改變,必須改變be[next]的值 de[next] = de[now] + 1; if(next == t) return true; } } } return false; } ``` 再來看 $dfs$ ,來判斷每一次的網路是否可以傳遞,完成增廣的過程(以下程式碼附上註釋)。這樣一次走了不止 $1$ 條增廣路,節省了不少時間。 ```cpp int dfs_Dinic(int now, int flow) { if(now == t)//找到匯點 return flow; int i, surp = flow;//當前剩餘流量 int SIZ = v[now].size(); for(i = be[now]; i < SIZ && surp; i++) { int next = v[now][i].to, valedge = v[now][i].value; if(valedge && de[next] == de[now] + 1) {//&&前判斷是否可以走,即是剩餘流量是否為0;&&後判斷是否滿足當前殘餘網路分層要求 int maxnow = dfs_Dinic(next, Min(surp, valedge)); if(!maxnow)//經驗定增廣完畢,de這個點不需要在遍歷了 de[next] = 0; v[now][i].value -= maxnow; v[next][v[now][i].rev].value += maxnow;//反悔,可能找到其他路徑比當前這個流大 surp -= maxnow; } } be[now] = i;//i之前的增廣路已經更新完 return flow - surp; } ``` 最後在來說說剪枝, $be$ 陣列,在遍歷 $i$ 時,$be[i]$ 之前的路徑已經找完增廣路了,而對於當前這個分層圖,不存在會有更優解的情況,程式也不需要反悔,並不會影響程式的正確性,所以直接就不需要遍歷之前的點。 # 時間複雜度 單看這段程式,可以發現時間複雜度為 $O(n^2m)$ 。 ```cpp int Dinic() { int res = 0, flow = 0; while(bfs_Dinic()) while(flow = dfs_Dinic(s, INF))//最大流的定義 res += flow;//流量守恆 return res; } ``` 而其實實際上並不需要這麼多時間,參考資料得知可以處理$10^4$~$10^5$這樣規模的網路。 # C++程式碼 ```cpp #include #include #include #include using namespace std; #define LL long long #define INF 0x3f3f3f3f #define Min(a, b) ((a) < (b) ? (a) : (b)) void Quick_Read(LL &N) { N = 0; LL op = 1; char c = getchar(); while(c < '0' || c > '9') { if(c == '-') op = -1; c = getchar(); } while(c >= '0' && c <= '9') { N = (N << 1) + (N << 3) + (c ^ 48); c = getchar(); } N *= op; } const LL MAXN = 2e2 + 5; struct Node { LL to, value, rev; Node() {} Node(LL T, LL V, LL R) { to = T; value = V; rev = R; } }; vector v[MAXN]; queue q; LL de[MAXN], be[MAXN]; LL n, m, s, t; bool bfs_Dinic() { memset(de, 0, sizeof(de)); while(!q.empty()) q.pop(); q.push(s); de[s] = 1; be[s] = 0; while(!q.empty()) { LL now = q.front(); q.pop(); LL SIZ = v[now].size(); for(int i = 0; i < SIZ; i++) { LL next = v[now][i].to; if(v[now][i].value && !de[next]) { q.push(next); be[next] = 0; de[next] = de[now] + 1; if(next == t) return true; } } } return false; } LL dfs_Dinic(LL now, LL flow) { if(now == t) return flow; int i, surp = flow; LL SIZ = v[now].size(); for(i = be[now]; i < SIZ && surp; i++) { LL next = v[now][i].to, valedge = v[now][i].value; if(valedge && de[next] == de[now] + 1) { LL maxnow = dfs_Dinic(next, Min(surp, valedge)); if(!maxnow) de[next] = 0; v[now][i].value -= maxnow; v[next][v[now][i].rev].value += maxnow; surp -= maxnow; } } be[now] = i; return flow - surp; } LL Dinic() { LL res = 0, flow = 0; while(bfs_Dinic()) while(flow = dfs_Dinic(s, INF)) res += flow; return res; } void Read() { LL A, B, C; Quick_Read(n); Quick_Read(m); Quick_Read(s); Quick_Read(t); for(int i = 1; i <= m; i++) { Quick_Read(A); Quick_Read(B); Quick_Read(C); LL idA = v[A].size(), idB = v[B].size(); v[A].push_back(Node(B, C, idB)); v[B].push_back(Node(A, 0, idA)); } } int main() { Read(); printf("%lld", Dinic()); return 0; } ``` [看看自己做對沒有吖](https://www.luogu.com.cn/proble