圖論專題-學習筆記:dinic 求解費用流
阿新 • • 發佈:2022-04-10
目錄
1. 前言
本篇博文將會重點講解 dinic 求解費用流。
費用流全稱:最小費用最大流,其一般的問題描述如下:
給出一張網路 \(G=<V,E>\),每條邊有兩個權值:\(f,v\)。
\(f\) 表示這條邊的最大流量,\(v\) 表示單位花費,也就是說從這條邊每流過一單位流量就要增加 \(v\) 的花費。
現在要求這張網路的最小費用最大流,也就是在保證總流量最大的情況下總費用最小。
之前筆者寫過一篇 EK 求解費用流,效率挺高,但是根據某法律:卡 EK 合法,卡 dinic 不合法!(筆者是真的不知道這法律哪來的)
於是 dinic 求解費用流還是需要掌握的。
在往下看之前,讀者應當對以下知識有所瞭解:
- dinic 求解最大流。
- SPFA 求解最短路。
沒有學過?
- dinic 求解最大流:演算法學習筆記:網路流#3——dinic 求解最大流
- SPFA 求解最短路:P3371 【模板】單源最短路徑(弱化版) 題解
2. 詳解
首先回顧一下 dinic 求解最大流的思路:
先採用 BFS 分層,在相鄰兩層之間推流,採用 DFS 形式推流,一次可以找到多條增廣路,採用當前弧優化。
那麼 dinic 如何求費用流呢?
因為出現了最小費用這一限制條件,因此我們不能簡單的對圖分層,而是先需要跑一遍最短路徑。
跑最短路徑的時候要特別注意:反向邊的費用為正向邊費用的相反數,因此 不能直接使用 dijkstra 演算法。
然後就可以愉快的 dinic 了~
因為此時已經滿足了最短路徑這一限制條件,也就是保證費用最小,因此此時直接類比 dinic 跑 DFS 就好了。
需要注意的是這裡的 dinic 有一個特別的限制條件:走過的點不能再走,這是為了防止被環卡掉。
記錄最小費用在中間記錄就好。
程式碼:
/* ========= Plozia ========= Author:Plozia Problem:P3381 【模板】最小費用最大流——dinic 寫法 Date:2021/3/30 ========= Plozia ========= */ #include <bits/stdc++.h> using std::queue; typedef long long LL; const int MAXN = 5e3 + 10, MAXM = 5e4 + 10; const LL INF = 0x3f3f3f3f3f3f3f3f; int n, m, s, t, Head[MAXN], cnt_Edge = 1, cur[MAXN]; LL dis[MAXN], ans_Flow, ans_Spend; bool book[MAXN], vis[MAXN]; struct node{int to; LL Flow, val; int Next;} Edge[MAXM << 1]; int read() { int sum = 0, fh = 1; char ch = getchar(); for (; ch < '0' || ch > '9'; ch = getchar()) fh -= (ch == '-') << 1; for (; ch >= '0' && ch <= '9'; ch = getchar()) sum = (sum << 3) + (sum << 1) + (ch ^ 48); return (fh == 1) ? sum : -sum; } void add_Edge(int u, int v, int w, int c) {++cnt_Edge; Edge[cnt_Edge] = (node){v, w, c, Head[u]}; Head[u] = cnt_Edge;} LL Min(LL fir, LL sec) {return (fir < sec) ? fir : sec;} bool SPFA() { queue <int> q; q.push(s); memset(book, 0, sizeof(book)); memset(dis, 0x3f, sizeof(dis)); book[s] = 1; dis[s] = 0; while (!q.empty()) { int x = q.front(); q.pop(); book[x] = 0; for (int i = Head[x]; i; i = Edge[i].Next) { int u = Edge[i].to; if (Edge[i].Flow && dis[u] > dis[x] + Edge[i].val) { dis[u] = dis[x] + Edge[i].val; if (!book[u]) {q.push(u); book[u] = 1;} } } } return dis[t] != INF; } LL dfs(int now, LL Flow) { if (now == t || Flow == 0) return Flow; vis[now] = 1; LL used = 0; for (int i = cur[now]; i; i = Edge[i].Next) { int u = Edge[i].to; cur[now] = i; if (Edge[i].Flow && !vis[u] && dis[now] + Edge[i].val == dis[u]) { LL Minn = dfs(u, Min(Flow - used, Edge[i].Flow)); if (Minn) { Edge[i].Flow -= Minn; Edge[i ^ 1].Flow += Minn; used += Minn; ans_Spend += Edge[i].val * Minn; if (used == Minn) {vis[now] = 0; return used;} } } } vis[now] = 0; if (used == 0) vis[now] = 1; return used; } void dinic() { while (SPFA()) { LL d; memset(vis, 0, sizeof(vis)); for (int i = 1; i <= n; ++i) cur[i] = Head[i]; while ((d = dfs(s, INF)) != 0) {ans_Flow += d;} } } int main() { n = read(), m = read(), s = read(), t = read(); for (int i = 1; i <= m; ++i) { int u = read(), v = read(), w = read(), c = read(); add_Edge(u, v, w, c); add_Edge(v, u, 0, -c); } dinic(); printf("%lld %lld\n", ans_Flow, ans_Spend); return 0; }
3. 總結
dinic 求解費用流的思路就是將 BFS 分層換成 SPFA 求解最短路,然後照常 DFS 就好。