CF708D Incorrect Flow 題解

rlc202204發表於2024-06-08

題意:

給定一張 \(n\) 個點 \(m\) 條邊的網路,源點為 \(1\),匯點為 \(n\)。對於每條邊,有容量 \(c\),當前流量 \(f\)

但這個圖是錯誤的,可能存在 \(c < f\),或者流量不守恆的情況。你每次操作可以將某條邊的 \(c\)\(f\)\(1\) 或減 \(1\)。請你用最少的操作次數將圖變成一個正確的網路。

\(n,m \le 100\)\(c,f \le 10^6\)\(1\) 沒有入邊,\(n\) 沒有出邊。

思路:

被我秒了的黑題,自信心爆棚。

先只考慮只要求流量守恆的情況,發現這就是一個類似於上下界網路流的東西,我們對於 \((u,v,f)\),建 \((v, u, f, 1)\)\((u, v, f, 1)\),分別代表減少這條邊的流量和增加這條邊的流量。

於是我們就可以類似的擴充套件到容量上了。我們把 \(c \ge f\)\(c < f\) 分開討論。

不難發現能不改變容量就儘量不要改變容量,我們依據這個來做。

如果 \(c \ge f\),連 \((v, u, f, 1)\) 表示可以減少到 \(0 \sim f\)\((u,v,c-f,1)\) 表示可以在不改變容量的情況下增加到 \(c\),以及 \((u,v,\infty,2)\) 表示如果還要增加就要兩個都增加了。

如果 \(c < f\),則我們考慮如果最後 \(f\)\(c\) 還要小,那肯定有 \(c\) 不變,而如果 \(f\)\(c\) 還要大,那也一定有 \(f = c\),否則一定也有 \(f=c\)

不難發先最後一種情況下花費一定是 \(f-c\),而剩下的兩個情況是在這個情況的基礎上增加或減少,所以我們先給答案加上 \(f-c\),然後連 \((v,u,f-c,0)\) 表示這個範圍內可以變化,然後 \((v,u,c,1)\) 表示再減少就要再花錢,\((u,v,\infty,2)\) 表示增加就要兩個都增加。

以上都是基於對於原有的邊流量一定是 \(c\),這是一個有下界的問題。

所以我們按照上下界有源匯網路流的做法建立超級源超級匯,同時連 \((n,1,\infty,0)\)

很有意思。

點選檢視程式碼
#include <iostream>
#include <vector>
#include <queue>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 105;
const int inf = 0x3f3f3f3f;

struct Edge {
	int to, val, cst, rev;
	Edge (int _to = 0, int _val = 0, int _cst = 0, int _rev = 0) :
		to(_to), val(_val), cst(_cst), rev(_rev) {}
};
vector<Edge> e[N];
void add(int u, int v, int w, int c) {
	e[u].push_back(Edge(v, w, c, (int)e[v].size()));
	e[v].push_back(Edge(u, 0, -c, (int)e[u].size() - 1));
}

int d[N] = {0};
bool inq[N] = {false};
bool bfs(int s, int t) {
	memset(d, inf, sizeof d);
	queue<int> q;
	q.push(s), d[s] = 0;
	while (!q.empty()) {
		int h = q.front();
		q.pop();
		inq[h] = false;
		for (auto i: e[h])
			if (i.val > 0 && d[i.to] > d[h] + i.cst) {
				d[i.to] = d[h] + i.cst;
				if (!inq[i.to])
					inq[i.to] = true, q.push(i.to);
			}
	}
	return d[t] != inf;
}

int cur[N] = {0};
bool vis[N] = {false};
int dfs(int x, int t, int f) {
	if (x == t)
		return f;
	int fl = 0;
	vis[x] = true;
	for (int i = cur[x]; i < (int)e[x].size(); i = ++cur[x])
		if (e[x][i].val > 0 && !vis[e[x][i].to] && d[e[x][i].to] == d[x] + e[x][i].cst) {
			fl = dfs(e[x][i].to, t, min(f, e[x][i].val));
			if (fl > 0) {
				e[x][i].val -= fl;
				e[e[x][i].to][e[x][i].rev].val += fl;
				break;
			}
		}
	if (fl == 0)
		d[x] = -1;
	vis[x] = false;
	return fl;
}

int Dinic(int s, int t) {
	int ans = 0;
	while (bfs(s, t)) {
		memset(cur, 0, sizeof cur);
		int ad = 0;
		while ((ad = dfs(s, t, inf)) > 0)
			ans += ad * d[t];
	}
	return ans;
}

int n, m;

int s[N] = {0};

int main() {
	cin >> n >> m;
	int ans = 0;
	for (int i = 1, u, v, f, c; i <= m; i++) {
		cin >> u >> v >> c >> f;
		s[u] += f, s[v] -= f;
		if (c >= f) {
			add(v, u, f, 1);//流回來
			add(u, v, c - f, 1);//流過去,不改變 c
			add(u, v, inf, 2);//流過去,改變 c 
		}
		else {
			ans += f - c;//至少這麼多代價 
			add(v, u, f - c, 0);//在可接受範圍內 [c, f]
			add(v, u, c, 1);//需要降低流量
			add(u, v, inf, 2);//需要同時增加流量和容量 
		}
	}
	int S = 0, T = n + 1;
	for (int i = 1; i <= n; i++)
		if (s[i] > 0)
			add(i, T, s[i], 0);
		else
			add(S, i, -s[i], 0);
	add(n, 1, inf, 0);
	cout << ans + Dinic(S, T) << endl;
	return 0;
} 

相關文章