1. 程式人生 > 其它 >圖論專題-網路流-學習筆記:EK 求解費用流

圖論專題-網路流-學習筆記:EK 求解費用流

目錄

1. 前言

費用流,全稱最小費用最大流,是網路流的一個分支。

最小費用最大流的問題描述如下:

給出一張網路 \(G=<V,E>\),每條邊有兩個權值:\(f,v\)

\(f\) 表示這條邊的最大流量,\(v\) 表示單位花費,也就是說從這條邊每流過一單位流量就要增加 \(v\) 的花費。

現在要求這張網路的最小費用最大流,也就是在保證總流量最大的情況下總費用最小。

在往下看之前,您需要對以下知識有所瞭解:

  1. SPFA 最短路徑演算法。傳送門:P3371 【模板】單源最短路徑(弱化版) 題解
  2. EK 求解最大流。傳送門:演算法學習筆記:網路流#2——EK 求解最大流

2. 詳解

模板題:P3381 【模板】最小費用最大流

首先回顧一下 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 求解費用流。

傳送門:演算法學習筆記:網路流#6——dinic 求解費用流