文章目录
一、无源汇-有上下界可行流
1.1问题描述
n 个点,m 条边,每条边 e 有一个流量下界 lower(e) 和流量上界 upper(e),求一种可行方案使得在所有点满足流量平衡条件的前提下,所有边满足流量限制。
1.2思路分析
1.2.1与普通最大流问题对比
- 上下界可行流仍然满足斜对称性,流量守恒,唯一不同的是容量限制中,每条边的流量有了下界,换言之,普通最大流问题其实就是流量下界为0的流网络上的问题。
- 图中没有源点和汇点
无论是EK算法还是Dinic算法都是解决最大流问题的算法,我们是否可以对原图进行改进从而将其转化为某个新图的最大流问题呢?
1.2.2容量差分
对于问题中的图,满足lower(u, v) <= f(u, v) <= upper(u, v),我们对不等式两边同时减去lower(u, v),得到
0
≤
f
(
u
,
v
)
−
l
o
w
e
r
(
u
,
v
)
≤
u
p
p
e
r
(
u
,
v
)
−
l
o
w
e
r
(
u
,
v
)
0 \le f(u,v)-lower(u,v) \le upper(u,v)-lower(u,v)
0≤f(u,v)−lower(u,v)≤upper(u,v)−lower(u,v)
令f’(u, v) = f(u, v) - lower(u, v),c’(u, v) = upper(u, v) - lower(u, v),则有:
0
≤
f
′
(
u
,
v
)
≤
c
′
(
u
,
v
)
0 \le f_{}^{'}(u,v) \le c_{}^{'}(u,v)
0≤f′(u,v)≤c′(u,v)
我们发现,我们构造出了一个和普通最大流容量限制相同的图,唯一不同的地方在于没有源汇点
1.2.3虚拟源汇点
对于原图,由于每条边流量下界的存在,每个节点u都有进入流量的下界fin(u),和流出流量的下界fout(u)
f
i
n
(
u
)
=
∑
l
o
w
e
r
(
v
,
u
)
f
o
u
t
(
u
)
=
∑
u
p
p
e
r
(
u
,
v
)
fin(u)=\sum lower(v,u) \\ fout(u)=\sum upper(u,v)
fin(u)=∑lower(v,u)fout(u)=∑upper(u,v)
这样原图中的点可以分为三类:
- fin(u) = fout(u)
- fin(u) > fout(u)
- fin(u) < fout(u)
那么在新图中,由于我们使得每条边e都减去了lower(e)的流量,不妨令diff(u) = fin(u) - fout(u)就会导致新图中的点也能分为三类:
- diff(u) = 0
- diff(u) > 0
- diff(u) < 0
- 对于1类点,显然在新图中满足流量守恒,进等于出
- 对于2类点,在新图中,少进入了diff(u)的流量,流量不守恒,所以我们建立虚拟源点s,建立<s , u , diff(u)>的边
- 对于3类点,在新图中,少流出了diff(u)的流量,流量不守恒,所以我们建立虚拟汇点t,建立<u , t , -diff(u)>的边
这样在新图中,就构建出了满足流量守恒,斜对称,容量限制的流网络,并且新网络除源汇点的边也仍满足原图的容量限制。
1.2.4新图最大流与原图可行流的关系
- 对于原图的可行流,我们都可以将每条边减去流量下界,增加两个虚拟源汇点,构造出一个流网络,并且由于可行流每条边都满足原图流量下界,所以新构造的流网络的源点发出的边都是满流,即原图可行流可以构造出流网络的满流最大流。
- 对于构造出的流网络,如果存在满流最大流,那么我们将原图中的边都加上流量下界,撤销虚拟源汇点和相关边,一定可以得到原图的一个可行流。因为流网络满流保证了diff(u) > 0的点 进入的流量都大于等于fin(u), diff(u) < 0的点的出去的流量都大于等于fout(u),对于diff(u) = 0的点,也都满足流量限制。
可见,原图存在可行流<=>新流网络存在满流最大流
1.3算法实现
- diff[u]存储Σw<v,u> - Σw<u,v>,令tot = Σdiff[u],其中diff[u] > 0
- 构造源点s = 0,汇点t = n + 1
- 对diff[u] > 0的点,建边<s , u , diff[u]>,对diff[u] < 0的点,建边<u , t , diff[u]>
- 对原图的边都减去流量下界
- 跑dinic算法,如果最大流 = tot,那么存在可行流,原图边的流量即新图边的流量加上流量下界
1.4OJ练习
#115. 无源汇有上下界可行流 - 题目 - LibreOJ (loj.ac)
#include <iostream>
#include <cstdio>
#include <cstring>
#include <queue>
#include <algorithm>
using namespace std;
const int N = 210, M = (10200 + N) * 2, inf = 1e9;
struct edge
{
int v, c, nxt;
} edges[M];
int head[N], d[N], cur[N], diff[N]{0}, LOW[M], idx = 0, n, m, s, t;
inline void addedge(int u, int v, int c)
{
edges[idx] = {v, c, head[u]}, head[u] = idx++;
}
inline void add(int u, int v, int low, int up)
{
LOW[idx] = low, addedge(u, v, up - low), addedge(v, u, 0);
}
bool bfs() // 多路增广,分层搜索优化
{
memset(d, 0, sizeof(d));
queue<int> q;
q.emplace(s), d[s] = 1;
while (q.size())
{
int u = q.front();
q.pop();
for (int i = head[u]; ~i; i = edges[i].nxt)
{
int v = edges[i].v;
if (!d[v] && edges[i].c)
{
d[v] = d[u] + 1, q.emplace(v);
if (v == t)
return true;
}
}
}
return false;
}
int dfs(int u, int limit)
{
if (u == t)
return limit;
int ret = 0;
for (int i = cur[u]; ~i && limit; i = edges[i].nxt) // limit > 0 余量优化
{
cur[u] = i; // 当前弧优化
int v = edges[i].v;
if (d[v] == d[u] + 1 && edges[i].c)
{
int incf = dfs(v, min(limit, edges[i].c));
if (!incf)
d[v] = 0; // 剪枝优化
edges[i].c -= incf, edges[i ^ 1].c += incf, ret += incf, limit -= incf;
}
}
return ret;
}
int dinic()
{
int ret = 0;
while (bfs())
memcpy(cur, head, sizeof(head)), ret += dfs(s, inf);
return ret;
}
int main()
{
ios::sync_with_stdio(false), cin.tie(0), cout.tie(0), memset(head, -1, sizeof(head));
freopen("in.txt", "r", stdin);
freopen("out.txt", "w", stdout);
int a, b, c, d, tot = 0;
cin >> n >> m, s = 0, t = n + 1;
for (int i = 0; i < m; i++)
{
cin >> a >> b >> c >> d;
add(a, b, c, d);
diff[a] -= c, diff[b] += c;
}
for (int i = 1; i <= n; i++)
{
if (diff[i] > 0)
add(s, i, 0, diff[i]), tot += diff[i];
else if (diff[i] < 0)
add(i, t, 0, -diff[i]);
}
if (dinic() == tot)
{
cout << "YES" << '\n';
for (int i = 0; i < m * 2; i += 2)
cout << edges[i ^ 1].c + LOW[i] << '\n';
}
else
{
cout << "NO";
}
return 0;
}
二、有源汇-有上下界最大流
2.1问题描述
n 个点,m 条边,每条边 e 有一个流量下界 lower(e) 和流量上界 upper(e),给定源点 s 与汇点 t,求源点到汇点的最大流。
2.2思路分析
2.2.1与无源汇-有上下界可行流问题对比
显然,就是在与无源汇-有上下界可行流问题基础上,增加源点与汇点,这样一来就从图变成了流网络,自然存在最大流。
那么如何找到最大流?有源汇问题是否可以转换无源汇问题?
2.2.2源汇虚拟边
我们发现添加<t, s, inf>(inf代表无穷大),这样就构造出了和无源汇-有上下界可行流中一样的图G‘-无源汇、有上下界限制
2.2.3虚拟源汇点
我们在G’中再增加虚拟源汇点S,T,执行和无源汇问题中相同的建图操作,将diff[u] > 0的点和S连边,将diff[u] < 0的点和T连边>。
2.2.4建立新图的作用
我们上面的操作:原图 => 和无源汇相同的图
那么我们有机会求出原图的一个可行流,不妨假设可行流存在,但是题目要求最大流,那么这个可行流和最大流能否建立联系?
我们上面只增加了(s, t)的一条边(其实是两条,包括反向边),那么我们求出可行流f0后,在残余网络中撤销这条边,再次在原图上,以原图的源汇点作为源汇点跑最大流可以得到从s到t的最大流df(注意我们只撤销了边,并未加上每条边e的lower(e)),由于S和T都是满流状态(相关边没有剩余容量),所以此次最大流操作不会涉及S和T的相关边。
那么根据可行流的可叠加性(证明见《算法导论》)|f0 + df| = f,为原图的一个可行流,并且它是原图的最大流。
为什么呢?
- 对于原图任意可行流Gf,我们一定可以在新图中找到s到t的一个可行流df,使得f0 + df = Gf
- 对于新图中s到t的任一可行流df,使f0 + df一定可以得到原图的一个可行流
- 即原图可行流 和 新图(撤销t到s边)残留网络s 到 t的可行流存在双射关系。
所以若想得到原图最大流,新图(撤销t到s边)残留网络s 到 t的最大流
2.3算法实现
- diff[u]存储Σw<v,u> - Σw<u,v>,令tot = Σdiff[u],其中diff[u] > 0
- 构造虚拟源点S = 0,虚拟汇点T = n + 1
- 对diff[u] > 0的点,建边<s , u , diff[u]>,对diff[u] < 0的点,建边<u , t , diff[u]>
- 对原图的边都减去流量下界,增加t到s容量上界正无穷,下界为0的边
- 跑dinic算法,如果最大流 = tot,那么存在可行流
- 撤销t到s的边,在残留网络上找s到t的最大流df,那么原图最大流即为tot + df
2.4OJ练习
#116. 有源汇有上下界最大流 - 题目 - LibreOJ (loj.ac)
#include <iostream>
#include <algorithm>
#include <cstring>
#include <queue>
using namespace std;
#define IOTIE ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
const int N = 205, M = (N + 10000) << 1, inf = 1e9;
struct edge
{
int v, c, nxt;
} edges[M];
int head[N], idx = 0, cur[N], d[N], diff[N], n, m, s, t, S, T, tot = 0;
inline void addedge(int u, int v, int c)
{
edges[idx] = {v, c, head[u]}, head[u] = idx++;
}
inline void add(int u, int v, int low, int up)
{
addedge(u, v, up - low), addedge(v, u, 0);
}
bool bfs()
{
memset(d, 0, sizeof(d));
queue<int> q;
q.emplace(s), d[s] = 1;
while (q.size())
{
int u = q.front();
q.pop();
for (int i = head[u]; ~i; i = edges[i].nxt)
{
int v = edges[i].v;
if (!d[v] && edges[i].c)
{
d[v] = d[u] + 1, q.emplace(v);
if (v == t)
return true;
}
}
}
return false;
}
int dfs(int u, int limit)
{
if (u == t)
return limit;
int ret = 0;
for (int i = head[u]; ~i && limit > 0; i = edges[i].nxt) // 余量
{
int v = edges[i].v;
cur[u] = i; // 当前弧
if (d[v] == d[u] + 1 && edges[i].c)
{
int incf = dfs(v, min(limit, edges[i].c));
if (!incf)
d[v] = 0; // 剪枝
edges[i].c -= incf, edges[i ^ 1].c += incf, limit -= incf, ret += incf;
}
}
return ret;
}
int dinic()
{
int ret = 0;
while (bfs())
memcpy(cur, head, sizeof(head)), ret += dfs(s, inf);
return ret;
}
int main()
{
IOTIE
freopen("in.txt", "r", stdin);
cin >> n >> m >> S >> T, s = 0, t = n + 1, memset(head, -1, sizeof(head));
int a, b, c, d;
for (int i = 0; i < m; i++)
cin >> a >> b >> c >> d, add(a, b, c, d), diff[a] -= c, diff[b] += c;
for (int i = 1; i <= n; i++)
if (diff[i] > 0)
add(s, i, 0, diff[i]), tot += diff[i];
else if (diff[i] < 0)
add(i, t, 0, -diff[i]);
add(T, S, 0, inf);
if (dinic() == tot)
{
int ans = edges[idx - 1].c;
s = S, t = T;
edges[idx - 1].c = edges[idx - 2].c = 0;
cout << ans + dinic();
}
else
cout << "please go home to sleep";
return 0;
}
三、有源汇有上下界最小流
3.1问题描述
n 个点,m 条边,每条边 e 有一个流量下界 lower(e) 和流量上界 upper(e),给定源点 s 与汇点 t,求源点到汇点的最小流。
3.2问题转化
由于已经证明原图可行流 和 新图(撤销t到s边)残留网络s 到 t的可行流df存在双射关系。,那么原图可行流最小<=>
<=> 新图(撤销t到s边)残留网络s 到 t的可行流df最小
<=>新图(撤销t到s边)残留网络t 到 s的可行流df’最最大
所以我们在找到可行流后,跑t到s的最大流即可,不再给出算法实现流程,直接看代码。
3.3OJ练习
#117. 有源汇有上下界最小流 - 题目 - LibreOJ (loj.ac)
#include <iostream>
#include <cstring>
#include <queue>
#include <algorithm>
using namespace std;
const int N = 50010, M = (N + 125003) << 1, inf = 2147483647;
int n, m, s, t, head[N], idx = 0, cur[N], d[N], S, T, A[N];
struct edge
{
int v, c, nxt;
} edges[M];
void addedge(int u, int v, int c)
{
edges[idx] = {v, c, head[u]}, head[u] = idx++;
}
void add(int u, int v, int c)
{
addedge(u, v, c), addedge(v, u, 0);
}
bool bfs()
{
memset(d, 0, sizeof(d));
queue<int> q;
q.emplace(s), d[s] = 1;
while (q.size())
{
int u = q.front();
q.pop();
for (int i = head[u]; ~i; i = edges[i].nxt)
{
int v = edges[i].v;
if (!d[v] && edges[i].c)
{
d[v] = d[u] + 1, q.emplace(v);
if (v == t)
return true;
}
}
}
return false;
}
int dfs(int u, int limit)
{
if (u == t)
return limit;
int res = 0;
for (int i = cur[u]; ~i && limit; i = edges[i].nxt)
{
cur[u] = i;
int v = edges[i].v;
if (d[v] == d[u] + 1 && edges[i].c)
{
int incf = dfs(v, min(edges[i].c, limit));
if (!limit)
d[v] = 0;
limit -= incf, edges[i].c -= incf, edges[i ^ 1].c += incf, res += incf;
}
}
return res;
}
int dinic()
{
int res = 0;
while (bfs())
memcpy(cur, head, sizeof(head)), res += dfs(s, inf);
return res;
}
int main()
{
ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
// freopen("in.txt", "r", stdin);
cin >> n >> m >> S >> T;
s = 0, t = n + 1;
memset(head, -1, sizeof(head));
for (int i = 0, a, b, c, d; i < m; i++)
cin >> a >> b >> c >> d, add(a, b, d - c), A[a] -= c, A[b] += c;
int tot = 0;
for (int i = 1; i <= n; i++)
if (A[i] > 0)
tot += A[i], add(s, i, A[i]);
else if (A[i] < 0)
add(i, t, -A[i]);
add(T, S, inf);
if (dinic() == tot)
{
int res = edges[idx - 1].c;
s = T, t = S;
edges[idx - 1].c = edges[idx - 2].c = 0;
cout << res - dinic();
}
else
cout << "please go home to sleep";
return 0;
}