圖論專題-網路流-學習筆記:EK 求解費用流
1. 前言
費用流,全稱最小費用最大流,是網路流的一個分支。
最小費用最大流的問題描述如下:
給出一張網路 \(G=<V,E>\),每條邊有兩個權值:\(f,v\)。
\(f\) 表示這條邊的最大流量,\(v\) 表示單位花費,也就是說從這條邊每流過一單位流量就要增加 \(v\) 的花費。
現在要求這張網路的最小費用最大流,也就是在保證總流量最大的情況下總費用最小。
在往下看之前,您需要對以下知識有所瞭解:
- SPFA 最短路徑演算法。傳送門:P3371 【模板】單源最短路徑(弱化版) 題解
- EK 求解最大流。傳送門:演算法學習筆記:網路流#2——EK 求解最大流
2. 詳解
首先回顧一下 EK 求解最大流的思路:
採用 BFS 來不斷尋找增廣路,每一次找到一條之後就更新這一條邊上的流量,同時建立反向邊進行反悔。
但是現在加了一條限制:總流量最大之外總費用最小。
啊這?這要怎麼做啊?
假設現在從 \(s\) 到 \(t\) 有兩條路徑。
我們分類討論一下:
- 如果這兩條路徑能夠流過的流量相同,那麼我們需要求出這兩條費用較小的一條,這個可以用最短路來尋找增廣路。
- 如果這兩條路徑能夠流過的流量不同:
- 如果流量較大那條邊費用比較小,那麼最短路尋找增廣路的時候一定會找到這條路徑,於是保證流量最大且費用最小。
- 但是如果流量較大那條邊費用比較大呢?那麼第一次跑最短路的時候找到的是另外那條邊,正確性似乎沒了保證。
- 可是,流量較大那條邊因為第一次沒有被遍歷到,因此它是圖中的一條 增廣路。而根據 EK 求解最大流的步驟,只要有增廣路就會繼續推流,而且沒有流量的邊是會被忽略掉的。這樣,即使第一次找的是流量花費較小的邊,流量花費較大的邊在後面也會被找到。
綜上,只需要使用最短路來代替 EK 中的 BFS 就可以找到最小費用最大流了。
但是怎麼求最短路呢?
某同學:筆者是不是沒學過圖論啊,這不是裸的 dijkstra 就好了嗎?
如果您的選擇是 dijkstra,那麼您就需要先對圖進行一定的處理。
為什麼?看一下下面加粗的部分:
(在編輯介面截的圖,可能與閱讀介面有點不一樣)
反向邊!
在 EK 中,反向邊是拿來反悔用的,也就是給出一次回退的機會。
而如果要反悔,流回去的流量是要減少一定量的花費的。
於是反向邊的花費是正向邊花費的相反數。
於是這張圖有了負權邊。
於是 dijkstra 死了
某同學:啊不是,話說 SPFA 不是很容易卡的嗎?dijkstra 沒用了?
實際上如果對網路進行一些奇怪的處理之後是可以使用 dijkstra,但是一般網路流的題目主要考查建模能力,用 SPFA 也不會太差。
當然真的被卡了筆者也沒辦法,還是老老實實寫 dijkstra 吧。
程式碼:
/*
========= Plozia =========
Author:Plozia
Problem:P3381 【模板】最小費用最大流——EK 寫法
Date:2021/3/23
========= Plozia =========
*/
#include <bits/stdc++.h>
using std::queue;
typedef long long LL;
const int MAXN = 5e3 + 10, MAXM = 5e4 + 10, INF = 0x7f7f7f7f;
int n, m, s, t, dis[MAXN], Flow[MAXN], Head[MAXN], cnt_Edge = 1, pre[MAXN], ans_flow, ans_spend;
struct EDGE {int to, w, c, Next;} Edge[MAXM << 1];
bool book[MAXN];
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) {Edge[++cnt_Edge] = (EDGE){v, w, c, Head[u]}; Head[u] = cnt_Edge;}
int Min(int fir, int sec) {return (fir < sec) ? fir : sec;}
bool SPFA()
{
queue <int> q; q.push(s);
memset(dis, 0x7f, sizeof(dis));
memset(Flow, 0x7f, sizeof(Flow));
memset(pre, 0, sizeof(pre));
memset(book, 0, sizeof(book));
dis[s] = 0; book[s] = 1;
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].w > 0 && dis[u] > dis[x] + Edge[i].c)
{
dis[u] = dis[x] + Edge[i].c;
Flow[u] = Min(Edge[i].w, Flow[x]);
pre[u] = i;
if (!book[u]) {book[u] = 1; q.push(u);}
}
}
}
return dis[t] != INF;
}
void EK()
{
while (SPFA())
{
int x = t;
for (; x != s; )
{
int k = pre[x];
Edge[k].w -= Flow[t];
Edge[k ^ 1].w += Flow[t];
x = Edge[k ^ 1].to;
}
ans_flow += Flow[t];
ans_spend += Flow[t] * dis[t];
}
}
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);
}
EK(); printf("%d %d\n", ans_flow, ans_spend);
return 0;
}
3. 總結
EK 求解費用流就是用 SPFA 來代替 BFS 即可。
但是根據某法律:卡 EK 合法,卡 dinic 不合法!(筆者是真的不知道這個法律哪來的),因此還需要掌握 dinic 求解費用流。