文章目录
1. 基本概念
1.1 流网络
是一个有向图,可以有环。有两个特殊点,一个叫源点,一个叫汇点;每条边有一定的容量,这个图就被称为一个流网络。
G = ( V , E ) G = (V, E) G=(V,E) ,假设图中不存在反向边。
1.2 可行流
指定每条边的一个流量,当且仅当满足以下两个条件时,我们称其为可行流 f f f:
- 容量限制, 0 ≤ f ( u , v ) ≤ C ( u , v ) 0 \le f(u, v) \le C(u, v) 0≤f(u,v)≤C(u,v)
- 流量守恒, 除了源点和汇点以外,任何一个点流入的流量等于流出的流量。
可行流的流量
∣
f
∣
|f|
∣f∣ , 定义为每秒从源点流出的流量减去每秒流入源点的流量。
∣
f
∣
=
Σ
(
S
,
V
)
∈
E
f
(
S
,
V
)
−
Σ
(
V
,
E
)
∈
E
f
(
V
,
S
)
|f| = \Sigma_{(S,V) \in E}f(S, V) - \Sigma_{(V, E) \in E} f(V, S)
∣f∣=Σ(S,V)∈Ef(S,V)−Σ(V,E)∈Ef(V,S)
最大流一般指最大可行流,也就是指流量的最大的可行流。
1.3 残留网络
残留网络 G f G_f Gf , V f = V , E f = E 和 E 的全部反向边 V_f = V, E_f = E 和 E的全部反向边 Vf=V,Ef=E和E的全部反向边 。
残留网络中容量的定义: C ′ ( U , V ) C^{'}(U,V) C′(U,V) ,
-
C ( U , V ) − f ( U , V ) , ( U , V ) ∈ E C(U, V) - f(U, V), (U, V) \in E C(U,V)−f(U,V),(U,V)∈E , 原图中的边
-
f ( V , U ) f(V, U) f(V,U), ( V , U ) ∈ E (V, U) \in E (V,U)∈E, 原图中边的反向边。
原网络的可行流加上它对应残留网络的可行流也是原网络的一个可行流。
∣ f + f ′ ∣ = ∣ f ∣ + ∣ f ′ ∣ |f + f^{'}| = |f| + |f^{'}| ∣f+f′∣=∣f∣+∣f′∣
残留网络中如果没有可行流,那么原网络一定是最大流。
1.4 增广路径
在残留网络中,沿着容量大于0的边走,如果能够走到终点的话,那么这条路径就被称为增广路径.
如果在一个可行流对应的残留网络中不存在增广路径,那么该可行流是最大流。证明将在后续给出。
1.5 割
1.5.1割的定义
设网络流 G = ( V , E ) G = (V,E) G=(V,E)。将点集 V V V分为两个部分 ( S , T ) (S, T) (S,T),满足$S \cup T = V, S \cap V = \empty 。并且源点属于 。并且源点属于 。并且源点属于S ,汇点属于 ,汇点属于 ,汇点属于T$。
1.5.2 割的容量
所有从 S S S指向 T T T的边的容量之和,记为 C ( S , T ) = ∑ u ∈ S ∑ v ∈ V C ( u , v ) C(S, T) = \sum_{u \in S} \sum_{v\in V} C(u, v) C(S,T)=∑u∈S∑v∈VC(u,v),最小割指的是最小的割的容量,注意与最大流的区别。
1.5.3 割的流量
所有从 S S S流过去的流量再减去从 T T T流回来的流量,记为 f ( S , T ) f(S, T) f(S,T).
对于任意一个割,割的流量一定小于等于割的容量
1.5.4 对于任意一个割,割的流量一定等于可行流的流量
1.5.5 对于任何一个流网络而言,它的任何一个可行流的流量都小于等于任何一个割的容量。
所以最大流的流量就等于最小割的容量。
1.5.6最大流最小割定理
三个等价条件:
- 一个可行流 f f f是最大流。
- 可行流 f f f的残留网络中不存在增广路。
- 存在某一个割 [ S , T ] [S, T] [S,T] ,使得可行流的流量等于割的容量。
证明:
①=>②:
反证法:假设当前已经是最大流,并且残留网络中还存在一个增广路时,那么当前可行流的流量就可以继续增加,说明原网络不是最大流,与假设矛盾。所以得证,一个可行流是最大流时,该可行流的残留网络中不存在增广路。
③=>①:
由于任何一个流的流量都小于等于任何一个割的容量。所以最大流一定小于等于最小割。
∣ f ∣ ≤ |f|\le ∣f∣≤最大流,又因为 ∣ f ∣ = C ( S , T ) ≥ |f| = C(S, T) \ge ∣f∣=C(S,T)≥ 最大流。所以 ∣ f ∣ |f| ∣f∣ = 最大流。
②=>③:
S S S: 在残留网络中,从源点 s s s出发沿容量大于0的边走,所有能到达的点的集合。
T T T : V − S V - S V−S 。
由于不存在增广路,所以 s s s一定不能走到 t t t。
对于这样的一个割,在原网络中,从 S S S 到 T T T 中的所有边的流量一定等于该边的容量 , 所有从 T T T 到 S S S 的边的流量等于0 。
由于 ∣ f ∣ = f ( S , T ) |f| = f(S, T) ∣f∣=f(S,T) = ∑ u ∈ S ∑ v ∈ T f ( u , v ) \sum_{u \in S} \sum_{v \in T} f(u, v) ∑u∈S∑v∈Tf(u,v) = ∑ u ∈ S ∑ v i n T C ( u , v ) \sum_{u \in S} \sum_{v in T} C(u, v) ∑u∈S∑vinTC(u,v) = C ( S , T ) C(S, T) C(S,T)。
1.6 算法
1.6.1 EK算法(O( n m 2 nm^2 nm2))
#include<iostream>
#include<cstring>
#include<queue>
using namespace std;
const int maxn = 1010, M = 20010, inf = 1e8;
int h[maxn], e[M], f[M], ne[M], idx;
void add(int a, int b, int c) // 维护的是残留网络
{
e[idx] = b, f[idx] = c, ne[idx] = h[a], h[a] = idx ++;
e[idx] = a, f[idx] = 0, ne[idx] = h[b], h[b] = idx ++;
}
int d[maxn], pre[maxn]; // d数组维护从起点到当前点的最小容量值,pre记录当前点上一条边
bool st[maxn];
int n, m, S, T;
bool bfs()
{
memset(st, 0, sizeof st);
queue<int> q;
q.push(S);
st[S] = true, d[S] = inf; // 每次将起点置为正无穷
while(q.size())
{
auto t = q.front();
q.pop();
for(int i = h[t]; ~i; i = ne[i]) {
int j = e[i];
if(!st[j] && f[i]) {
st[j] = true;
d[j] = min(d[t], f[i]);
pre[j] = i;
if(j == T) return true;
q.push(j);
}
}
}
return false;
}
int EK()
{
int r = 0;
while(bfs())
{
r += d[T];
for(int i = T; i != S; i = e[pre[i] ^ 1]) {
f[pre[i]] -= d[T], f[pre[i] ^ 1] += d[T]; // 在残留网络中,正向边减去,反向边加上
}
}
return r;
}
int main()
{
cin >> n >> m >> S >> T;
memset(h, -1, sizeof h);
while(m --){
int a, b, c; cin >> a >> b >> c;
add(a, b, c);
}
cout << EK() << endl;
}
1.6.2 dinic算法( O ( n 2 m ) O(n^2m) O(n2m))
#include<iostream>
#include<cstring>
#include<queue>
using namespace std;
const int maxn = 10010, M = 200010, inf = 1e8;
int h[maxn], e[M], f[M], ne[M], idx;
int d[maxn], pre[maxn], cur[maxn];
int n, m, S, T;
void add(int a, int b, int c)
{
e[idx] = b, f[idx] = c, ne[idx] = h[a], h[a] = idx ++;
e[idx] = a, f[idx] = 0, ne[idx] = h[b], h[b] = idx ++;
}
bool bfs()
{
memset(d, -1, sizeof d);
queue<int> q;
q.push(S); d[S] = 0, cur[S] = h[S];
while(q.size())
{
auto t = q.front();
q.pop();
for(int i = h[t]; i != -1; i = ne[i]) {
int j = e[i];
if(d[j] == -1 && f[i]) {
d[j] = d[t] + 1;
cur[j] = h[j];
if(j == T) return true;
q.push(j);
}
}
}
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 = ne[i]) {
cur[u] = i; // 当前弧优化
int j = e[i];
if(d[j] == d[u] + 1 && f[i]) {
int t = find(j, min(f[i], limit - flow));
if(!t) d[j] = -1; // 删除废点
f[i] -= t, f[i ^ 1] += t, flow += t;
}
}
return flow;
}
int dinic()
{
int r = 0, flow;
while(bfs()) while(flow = find(S, inf)) r += flow;
return r;
}
int main()
{
ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
cin >> n >> m >> S >> T;
memset(h, -1, sizeof h);
while(m --){
int a, b, c; cin >> a >> b >> c;
add(a, b, c);
}
cout << dinic() << endl;
return 0;
}
1.6.3 求二分图的最大匹配
源点向左半边的点分别连一条容量为1的边,右半边的点分别向汇点连一条容量为一的边,然后根据数据由左边的点向右边的点连一条容量为1的边,用dinic求一个最大流就是答案。
#include<iostream>
#include<cstring>
#include<queue>
using namespace std;
#define endl '\n'
const int maxn = 110, M = 5210, inf = 1e8;
int h[maxn], e[M], f[M], ne[M], idx;
int d[maxn], cur[maxn];
int n, m;
int S, T;
void add(int a, int b, int c)
{
e[idx] = b, f[idx] = c, ne[idx] = h[a], h[a] = idx ++;
e[idx] = a, f[idx] = 0, ne[idx] = h[b], h[b] = idx ++;
}
bool bfs()
{
memset(d, -1, sizeof d);
queue<int> q;
q.push(S); d[S] = 0; cur[S] = h[S];
while(q.size())
{
auto t = q.front();
q.pop();
for(int i = h[t]; i != -1; i = ne[i]) {
int j = e[i];
if(d[j] == -1 && f[i]) {
d[j] = d[t] + 1;
cur[j] = h[j];
if(j == T) return true;
q.push(j);
}
}
}
return false;
}
int find(int u, int limit)
{
if(u == T) return limit;
int flow = 0;
for(int i = h[u]; i != -1 && flow < limit; i = ne[i]) {
int j = e[i];
cur[u] = i;
if(d[j] == d[u] + 1 && f[i]) {
int t = find(j, min(f[i], limit - flow));
if(!t) d[j] = -1;
f[i] -= t, f[i ^ 1] += t, flow += t;
}
}
return flow;
}
int dinic()
{
int r = 0, flow;
while(bfs()) while(flow = find(S, inf)) r += flow;
return r;
}
int main()
{
memset(h, -1, sizeof h);
cin >> m >> n;
S = 0, T = n + 1;
for(int i = 1; i <= m; i ++) add(S, i, 1);
for(int i = m + 1; i <= n; i ++) add(i, T, 1);
int a, b;
while(cin >> a >> b, a != -1) add(a, b, 1);
cout << dinic() << endl;
for(int i = 0; i < idx; i += 2) {
if(e[i] > m && e[i] <= n && !f[i]) {
cout << e[i ^ 1] << ' ' << e[i] << endl;
}
}
return 0;
}
1.6.4 二分图的多重匹配问题
#include<iostream>
#include<cstring>
#include<queue>
using namespace std;
const int maxn = 510, M = (150 * 270 + maxn) * 2, inf = 1e8;
int h[maxn], e[M], f[M], ne[M], idx;
int d[maxn], cur[maxn];
int m, n, S, T;
void add(int a, int b, int c)
{
e[idx] = b, f[idx] = c, ne[idx] = h[a], h[a] = idx ++;
e[idx] = a, f[idx] = 0, ne[idx] = h[b], h[b] = idx ++;
}
bool bfs()
{
memset(d, -1, sizeof d);
queue<int> q;
q.push(S); d[S] = 0, cur[S] = h[S];
while(q.size())
{
auto t = q.front();
q.pop();
for(int i = h[t]; i != -1; i = ne[i]) {
int j = e[i];
if(d[j] == -1 && f[i]) {
d[j] = d[t] + 1;
cur[j] = h[j];
if(j == T) return true;
q.push(j);
}
}
}
return false;
}
int find(int u, int limit)
{
if(u == T) return limit;
int flow = 0;
for(int i = h[u]; i != -1 && flow < limit; i = ne[i]) {
int j = e[i];
cur[u] = i;
if(d[j] == d[u] + 1 && f[i]) {
int t = find(j, min(f[i], limit - flow));
if(!t) d[j] = -1;
f[i] -= t, f[i ^ 1] += t, flow += t;
}
}
return flow;
}
int dinic()
{
int r = 0, flow;
while(bfs()) while(flow = find(S, inf)) r += flow;
return r;
}
int main()
{
cin >> m >> n;
S = 0, T = m + n + 1;
memset(h, -1, sizeof h);
int sum = 0;
for(int i = 1; i <= m; i ++){
int c; cin >> c;
sum += c;
add(S, i, c);
}
for(int i = 1; i <= n; i ++){
int c; cin >> c;
add(i + m, T, c);
}
for(int i = 1; i <= m; i ++){
for(int j = 1; j <= n; j ++) {
add(i, j + m, 1);
}
}
if(dinic() != sum) cout << 0 << endl;
else {
cout << 1 << endl;
for(int i = 1; i <= m; i ++){
for(int j = h[i]; ~j; j = ne[j]) {
int t = e[j];
if(t > m && t <= m + n && !f[j]) {
cout << t - m << ' ';
}
}
cout << endl;
}
}
return 0;
}
1.7 无源汇上下界可行流
给定一个包含 n n n 个点 m m m 条边的有向图,每条边都有一个流量下界和流量上界。
求一种可行方案使得在所有点满足流量平衡条件的前提下,所有边满足流量限制。
#include<iostream>
#include<cstring>
#include<queue>
using namespace std;
#define endl '\n'
const int maxn = 210, M = (10200 + maxn) * 2, inf = 1e8;
int h[maxn], e[M], f[M], l[M], ne[M], idx;
int d[maxn], cur[maxn], A[maxn];
int n, m, S, T;
void add(int a, int b, int c, int d)
{
e[idx] = b, f[idx] = d - c, l[idx] = c, ne[idx] = h[a], h[a] = idx ++;
e[idx] = a, f[idx] = 0, ne[idx] = h[b], h[b] = idx ++;
}
bool bfs()
{
memset(d, -1, sizeof d);
queue<int> q;
d[S] = 0; cur[S] = h[S]; q.push(S);
while(q.size())
{
auto t = q.front();
q.pop();
for(int i = h[t]; ~i; i = ne[i]) {
int j = e[i];
if(d[j] == -1 && f[i]) {
d[j] = d[t] + 1;
cur[j] = h[j];
if(j == T) return true;
q.push(j);
}
}
}
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 = ne[i]) {
int j = e[i];
cur[u] = i;
if(d[j] == d[u] + 1 && f[i]) {
int t = find(j, min(f[i], limit - flow));
if(!t) d[j] = -1;
f[i] -= t, f[i ^ 1] += t, flow += t;
}
}
return flow;
}
int dinic()
{
int r = 0, flow;
while(bfs()) while(flow = find(S, inf)) r += flow;
return r;
}
int main()
{
cin >> n >> m;
S = 0, T = n + 1;
memset(h, -1, sizeof h);
for(int i = 0; i < m; i ++){
int a, b, c, d; cin >> a >> b >> c >> d;
add(a, b, c, d);
A[a] -= c, A[b] += c;
}
int sum = 0;
for(int i = 1; i <= n; i ++){
if(A[i] > 0) add(S, i, 0, A[i]), sum += A[i];
else if(A[i] < 0) add(i, T, 0, -A[i]);
}
if(dinic() != sum) cout << "NO" << endl;
else {
cout << "YES" << endl;
for(int i = 0; i < 2 * m; i += 2){
cout << f[i ^ 1] + l[i] << endl;
}
}
return 0;
}
1.8 有源汇上下界最大流
模板题: AcWing2189
在给定的汇点 t t t和源点 s s s之间连一条容量为正无穷的边,然后求一下虚拟源点 S S S和虚拟汇点 T T T之间的最大流,如果是满流的,我们就在当前的残留网络上把增加的这条边删掉,然后再求一下从 s s s到 t t t的最大流,求完之后,我们现有的流量再加上原有的流量就是我们最终的最大流。因为 s s s和 t t t都是中间节点,并不存储流量,所以原有的流量就是从 t t t流向 s s s加的那条边的流量。
#include<iostream>
#include<cstring>
#include<queue>
using namespace std;
const int maxn = 210, M = (10000 + maxn) * 2, inf = 1e8;
int h[maxn], e[M], f[M], ne[M], idx;
int d[maxn], cur[maxn], A[maxn];
int n, m, S, T, s, t;
void add(int a, int b, int c)
{
e[idx] = b, f[idx] = c, ne[idx] = h[a], h[a] = idx ++;
e[idx] = a, f[idx] = 0, ne[idx] = h[b], h[b] = idx ++;
}
bool bfs()
{
memset(d, -1, sizeof d);
queue<int> q;
q.push(S), d[S] = 0, cur[S] = h[S];
while(q.size())
{
auto t = q.front();
q.pop();
for(int i = h[t]; ~i; i = ne[i]) {
int j = e[i];
if(d[j] == -1 && f[i]) {
d[j] = d[t] + 1;
cur[j] = h[j];
if(j == T) return true;
q.push(j);
}
}
}
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 = ne[i]) {
int j = e[i];
if(d[j] == d[u] + 1 && f[i]) {
int t = find(j, min(f[i], limit - flow));
if(!t) d[j] = -1;
f[i] -= t, f[i ^ 1] += t, flow += t;
}
}
return flow;
}
int dinic()
{
int r = 0, flow;
while(bfs()) while(flow = find(S, inf)) r += flow;
return r;
}
int main()
{
cin >> n >> m >> s >> t;
S = 0, T = n + 1;
memset(h, -1, sizeof h);
for(int i = 0; i < m; i ++) {
int a, b, c, d; 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) 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 << "No Solution" << endl;
else {
int res = f[idx - 1];
S = s, T = t;
f[idx - 1] = f[idx - 2] = 0;
cout << res + dinic() << endl;
}
return 0;
}
1.9 有源汇上下界最小流
模板题: AcWing2190
#pragma GCC optimize(2)
#pragma GCC optimize(3)
#include<iostream>
#include<cstring>
#include<queue>
using namespace std;
const int maxn = 50010, M = (125010 + maxn) * 2, inf = 2147483647;
#define endl '\n'
int h[maxn], e[M], f[M], ne[M], idx;
int d[maxn], cur[maxn], A[maxn];
int n, m, S, T;
void add(int a, int b, int c)
{
e[idx] = b, f[idx] = c, ne[idx] = h[a], h[a] = idx ++;
e[idx] = a, f[idx] = 0, ne[idx] = h[b], h[b] = idx ++;
}
bool bfs()
{
memset(d, -1, sizeof d);
queue<int> q;
q.push(S), d[S] = 0, cur[S] = h[S];
while(q.size())
{
auto t = q.front();
q.pop();
for(int i = h[t]; ~i; i = ne[i]) {
int j = e[i];
if(d[j] == -1 && f[i]) {
d[j] = d[t] + 1;
cur[j] = h[j];
if(j == T) return true;
q.push(j);
}
}
}
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 = ne[i]) {
cur[u] = i;
int j = e[i];
if(d[j] == d[u] + 1 && f[i]) {
int t = find(j, min(f[i], limit - flow));
if(!t) d[j] = -1;
f[i] -= t, f[i ^ 1] += t, flow += t;
}
}
return flow;
}
int dinic()
{
int r = 0, flow;
while(bfs()) while(flow = find(S, inf)) r += flow;
return r;
}
int main()
{
ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
int s, t;
cin >> n >> m >> s >> t;
memset(h, -1, sizeof h);
S = 0, T = n + 1;
for(int i = 0; i < m; i ++){
int a, b, c, d; 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) 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 << "No Solution" << endl;
else {
int res = f[idx - 1];
S = t, T = s;
f[idx - 1] = f[idx - 2] = 0;
cout << res - dinic() << endl;
}
return 0;
}