CF708D Incorrect Flow 题解
题意:
给定一张 \(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;
}