【Coel.学习笔记】上下界网络流问题
上下界网络流包括无源汇上下界可行流、有源汇上下界最大最小流等。
无源汇上下界可行流
给定一个包含 \(n\) 个点 \(m\) 条边的有向图,每条边都有一个流量下界和流量上界。
求一种可行方案使得在所有点满足流量平衡条件的前提下,所有边满足流量限制。
解析:这道题的流量限制与正常的网络流不同,存在下界,即 \(C_l(u,v) \leq f(u,v) \leq C_u(u,v)\)。
思考一下怎么把下界转换为 0,即普通的流网络限制。不难想到利用不等式的性质,转化为 \(0\leq f(u,v)-C_l(u,v) \leq C_u(u,v) - C_l(u,v)\)。那么,让 \(C_u(u,v)-C_l(u,v)\) 作为新网络的边。但此时流量守恒不一定能够得到满足,需要进行调整。设初始的流入减流出量为 \(A\)。
- \(A=0\) 则流量平衡,不需要加附加边。
- \(A>0\) 时流入量过大,新建一个源点与该点相连,流量为 \(M\);
- \(A<0\) 时流出量过大,新建一个汇点与该点相连,流量与 \(-M\)。
如果附加边满流,就意味着这个点可以实现流量平衡。从新源点向新汇点做最大流,则全部满流时存在满足上下界的可行流。
代码如下:
// Problem: 无源汇上下界可行流
// Contest: AcWing
// URL: https://www.acwing.com/problem/content/2190/
// Memory Limit: 64 MB
// Time Limit: 1000 ms
//
// Powered by CP Editor (https://cpeditor.org)
#include <iostream>
#include <cstring>
#include <queue>
using namespace std;
const int maxn = 3e4 + 10, inf = 1e9;
int n, m, S, T, tot;
int head[maxn], nxt[maxn], to[maxn], c[maxn], l[maxn], cnt;
int d[maxn], cur[maxn], A[maxn];
void add(int u, int v, int dn, int up) {
nxt[cnt] = head[u], to[cnt] = v, c[cnt] = up - dn, l[cnt] = dn, head[u] = cnt++;
nxt[cnt] = head[v], to[cnt] = u, c[cnt] = 0, head[v] = cnt++;
}
bool bfs() {
queue<int> Q;
memset(d, -1, sizeof(d));
Q.push(S), d[S] = 0, cur[S] = head[S];
while (!Q.empty()) {
int u = Q.front();
Q.pop();
for (int i = head[u]; i != -1; i = nxt[i]) {
int v = to[i];
if (d[v] == -1 && c[i]) {
d[v] = d[u] + 1;
cur[v] = head[v];
if (v == T)
return true;
Q.push(v);
}
}
}
return false;
}
int find(int u, int limit) {
if (u == T)
return limit;
int flow = 0;
for (int i = cur[u]; i != -1 && flow < limit; i = nxt[i]) {
cur[u] = i;
int v = to[i];
if (d[v] == d[u] + 1 && c[i]) {
int t = find(v, min(c[i], limit - flow));
if (!t) d[v] = -1;
c[i] -= t, c[i ^ 1] += t, flow += t;
}
}
return flow;
}
int Dinic() {
int ans = 0, flow;
while (bfs())
while ((flow = find(S, inf)))
ans += flow;
return ans;
}
int main(void) {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> n >> m;
S = 0, T = n + 1;
memset(head, -1, sizeof(head));
for (int i = 1; i <= m; i++) {
int u, v, up, dn;
cin >> u >> v >> dn >> up;
add(u, v, dn, up);
A[u] -= dn, A[v] += dn;
}
for (int i = 1; i <= n; i++) {
if (A[i] > 0)
add(S, i, 0, A[i]), tot += A[i];
if (A[i] < 0)
add(i, T, 0, -A[i]);
}
if (Dinic() != tot)
cout << "NO" << '\n';
else {
cout << "YES" << '\n';
for (int i = 0; i < m * 2; i+= 2)
cout << c[i ^ 1] + l[i] << '\n';
}
return 0;
}
有源汇上下界最大流
洛谷传送门
(洛谷中这道题需要简单地建模为网络流问题,然后按照有源汇上下界最大流的方法做)
给定一个包含 \(n\) 个点 \(m\) 条边的有向图,每条边都有一个流量下界和流量上界。
给定源点 \(S\) 和汇点 \(T\),求源点到汇点的最大流。
解析:本题多了两个限制:有源汇,且要求最大流。
对于第一个限制,我们可以给汇点与源点连一条容量正无穷的边,这样所有点都可以实现流量守恒,可以巧妙地转化成无源汇问题。
对于第二个限制,则可以先求出任意一个可行流,不存在可行流自然最大流也不会存在;然后对残余网络做一遍最大流,将其与可行流流量相加便是答案。
代码如下:
// Problem: P5192 Zoj3229 Shoot the Bullet|东方文花帖|【模板】有源汇上下界最大流
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/P5192
// Memory Limit: 125 MB
// Time Limit: 1000 ms
//
// Powered by CP Editor (https://cpeditor.org)
#include <iostream>
#include <cstring>
using namespace std;
const int maxn = 1e6 + 10, inf = 1e9;
int n, m, s, t, S, T, tot;
int head[maxn], nxt[maxn], to[maxn], c[maxn], cnt;
int A[maxn], d[maxn], cur[maxn], q[maxn];
void add(int u, int v, int w) {
nxt[cnt] = head[u], to[cnt] = v, c[cnt] = w, head[u] = cnt++;
nxt[cnt] = head[v], to[cnt] = u, c[cnt] = 0, head[v] = cnt++;
}
bool bfs() {
memset(d, -1, sizeof(d));
int hh = 0, tt = -1;
q[++tt] = S, d[S] = 0, cur[S] = head[S];
while (tt >= hh) {
int u = q[hh++];
for (int i = head[u]; ~i; i = nxt[i]) {
int v = to[i];
if (d[v] == -1 && c[i]) {
d[v] = d[u] + 1;
cur[v] = head[v];
if (v == T) return true;
q[++tt] = v;
}
}
}
return false;
}
int find(int u, int limit) {
if (u == T) return limit;
int flow = 0;
for (int i = cur[u]; ~i && flow < limit; i = nxt[i]) {
int v = to[i];
cur[u] = i;
if (d[v] == d[u] + 1 && c[i]) {
int t = find(v, min(c[i], limit - flow));
if (!t) d[v] = -1;
c[i] -= t, c[i ^ 1] += t, flow += t;
}
}
return flow;
}
int dinic() {
int ans = 0, flow;
while (bfs())
while ((flow = find(S, inf)))
ans += flow;
return ans;
}
int main(void) {
while (cin >> n >> m) {
memset(head, -1, sizeof(head));
memset(A, 0, sizeof(A));
s = 0, t = n + m + 1, cnt = tot = 0;
S = n + m + 2, T = S + 1;
for (int i = 1, G; i <= m; i++) {
cin >> G;
add(i, t, inf - G);
A[i] -= G, A[t] += G;
}
for (int i = m + 1, C, D; i <= m + n; i++) {
cin >> C >> D;
add(s, i, D);
while (C--) {
int x, L, R;
cin >> x >> L >> R;
x++;
add(i, x, R - L);
A[i] -= L, A[x] += L;
}
}
for (int i = 0; i <= n + m + 1; i++) {
if (A[i] > 0) add(S, i, A[i]), tot += A[i];
else if (A[i] < 0) add(i, T, -A[i]);
}
add(t, s, inf);
if (dinic() < tot)
cout << -1 << '\n' << '\n';
else {
int res = c[cnt - 1];
c[cnt - 1] = c[cnt - 2] = 0;
S = s, T = t;
cout << res + dinic() << '\n' << '\n';
}
}
return 0;
}
有源汇上下界最小流
与上一题相似,不过把最大换成了最小。
解析:类似的操作,把可行流流量减去从汇点到源点的最大流即为答案。
直接把 S = s, T = t
换成 S = t, T = s
并注意一下数据范围即可,代码略。
多源汇最大流
有向图存在多个源点与汇点,求最大流。
解析:建立虚拟源点与虚拟汇点,分别与源汇点连上容量为正无穷的边,做最大流即可。
试着证明一下?首先对于虚拟源汇点之外的部分没有变化,因此流量守恒;然后对于那些源汇点而言,由于流入正无穷、流出正无穷,所以也满足流量守恒。因此原图与新图的可行流一一对应,原图最大流即为新图最大流。
代码如下:
// Problem: 多源汇最大流
// Contest: AcWing
// URL: https://www.acwing.com/problem/content/2236/
// Memory Limit: 64 MB
// Time Limit: 1000 ms
//
// Powered by CP Editor (https://cpeditor.org)
#include <iostream>
#include <cstring>
using namespace std;
const int maxn = 1e6 + 10, inf = 1e8;
int n, m, S, T;
int head[maxn], nxt[maxn], to[maxn], c[maxn], cnt;
int q[maxn], d[maxn], cur[maxn];
void add(int u, int v, int w) {
nxt[cnt] = head[u], to[cnt] = v, c[cnt] = w, head[u] = cnt++;
nxt[cnt] = head[v], to[cnt] = u, c[cnt] = 0, head[v] = cnt++;
}
bool bfs() {
int hh = 0, tt = 0;
memset(d, -1, sizeof(d));
q[0] = S, d[S] = 0, cur[S] = head[S];
while (hh <= tt) {
int u = q[hh++];
for (int i = head[u]; ~i; i = nxt[i]) {
int v = to[i];
if (d[v] == -1 && c[i]) {
d[v] = d[u] + 1;
cur[v] = head[v];
if (v == T) return true;
q[++tt] = v;
}
}
}
return false;
}
int find(int u, int limit) {
if (u == T)
return limit;
int flow =0 ;
for (int i = cur[u]; ~i && flow < limit; i = nxt[i]) {
cur[u] = i;
int v = to[i];
if (d[v] == d[u] + 1 && c[i]) {
int t = find(v, min(c[i], limit - flow));
if (!t) d[v] = -1;
c[i] -= t, c[i ^ 1] += t, flow += t;
}
}
return flow;
}
int dinic() {
int ans = 0, flow;
while (bfs())
while ((flow = find(S, inf)))
ans += flow;
return ans;
}
int main(void) {
ios::sync_with_stdio(false);
cin.tie(nullptr);
memset(head, -1, sizeof(head));
int sc, tc;
cin >> n >> m >> sc >>tc;
S = 0, T = n + 1;
for (int i = 1; i <= sc; i++) {
int x;
cin >> x;
add(S, x, inf);
}
for (int i = 1; i <= tc; i++) {
int x;
cin >> x;
add(x, T, inf);
}
for (int i = 1; i <= m; i++) {
int u, v, w;
cin >> u >> v >> w;
add(u, v, w);
}
cout << dinic();
return 0;
}