基本概念
流网络
对于一个有向图, 抽象成水管里的水的模型, 每根管子有容量限制, 计为
G
=
(
V
,
E
)
G = (V, E)
G=(V,E), 首先不考虑反向边
对于任意无向图, 都可以将反向边转化为上述形式
如果一条边不存在, 定义为容量为 0 0 0, 形式上来说就是 c ( u , v ) = 0 c(u, v) = 0 c(u,v)=0
可行流
对于每一个流网络考虑一个可行流 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)
- 流量守恒, 对于每个点来说, 流进去多少, 流出来多少, 形式化来说
∑
(
v
,
u
)
f
(
v
,
u
)
=
∑
(
u
,
v
)
f
(
u
,
v
)
\sum_{(v, u)} f(v, u) = \sum _{(u, v)} f(u, v)
(v,u)∑f(v,u)=(u,v)∑f(u,v)
对于一个可行流, 从源点流向汇点的流量定义为
∣
f
∣
|f|
∣f∣, 每秒从源点流出的流量, 或者流入汇点的流量, 每秒净往外流出的流量
∣ f ∣ = ∑ ( s , v ) f ( s , v ) − ∑ ( v , s ) f ( v , s ) |f| = \sum _{(s, v)} f(s, v) - \sum _{(v, s)} f(v, s) ∣f∣=(s,v)∑f(s,v)−(v,s)∑f(v,s)
最大流指的是流量值最大的可行流
残存网络
残存网络是对于流网络某一个可行流
对于某个可行流
f
1
f_1
f1, 残存网络
G
f
1
G_{f_1}
Gf1
残存网络的点集与原图完全一致
V
f
=
V
V_f = V
Vf=V, 但是边集不仅包含原图的边还包含原图的反向边
E
f
=
E
+
E
′
E_f =E + E '
Ef=E+E′
残存网络也是流网络
对于残存网络的容量记为 c ′ ( u , v ) c'(u, v) c′(u,v)
- 对于原图的边, 那么 c ′ ( u , v ) = c ( u , v ) − f ( u , v ) c'(u, v) = c(u, v) - f(u, v) c′(u,v)=c(u,v)−f(u,v)
- 对于原图的反向边, c ′ ( u , v ) = f ( v , u ) c'(u, v) = f(v, u) c′(u,v)=f(v,u)
对于任意一个可行流 f f f, 都可以求出残存网络 G f G_{f} Gf
记残存网络的可行流 f ′ f' f′, f + f ′ f + f' f+f′也是原来流网络 G G G的一个可行流
新的流的流量值等于两个流的流量值之和 ∣ f + f ′ ∣ = ∣ f ∣ + ∣ f ′ ∣ |f + f'| = |f| + |f'| ∣f+f′∣=∣f∣+∣f′∣, 流量相加等同于每条边相加
为什么新的流是原流网络 G G G的可行流?
- 是否满足容量限制
- 是否满足流量守恒
增广路径
在残存网络中, 从源点出发沿着**容量大于
0
0
0**的点走如果能走到汇点, 那么这一条路径就是增广路径
上述红色路径就是增广路径, 增广路径一定是一个可行流, 流量大于
0
0
0
如果
G
f
G_f
Gf总不存在增广路径, 断言
f
f
f是原来流网络的最大流
割
将点集 V V V分为两个集合 S , T S, T S,T, 满足以下条件
- s ∈ S s \in S s∈S, t ∈ T t \in T t∈T
- S ∪ T = V S \cup T = V S∪T=V, S ∩ T = ∅ S \cap T = \emptyset S∩T=∅
上述就是割的形式化描述
割的容量
所有从
S
S
S指向
T
T
T的边, 被称为割的容量, 容量不考虑回来的边
c ( S , T ) = ∑ u ∈ S , v ∈ T c ( u , v ) c(S, T) = \sum _{u\in S, v \in T} c(u, v) c(S,T)=u∈S,v∈T∑c(u,v)
割的流量
所有从 S S S留到 T T T的流量减去从 T T T流向 S S S的流量, 也就是净流量
f ( S , T ) = ∑ u ∈ S , v ∈ T f ( u , v ) − ∑ u ∈ T , v ∈ S f ( u , v ) f(S, T) = \sum _{u \in S, v \in T} f(u, v) - \sum _{u \in T, v \in S} f(u, v) f(S,T)=u∈S,v∈T∑f(u,v)−u∈T,v∈S∑f(u,v)
对于一个流网络来说, 如果流网络确定, 那么割的容量确定, 但是因为一个流网络存在多个可行流, 因此割的流量是取决于每个可行流的流量的
性质1: 最小割指的是最小割的容量, 对于任意一个割以及任意一个可行流, 割的流量小于等于割的容量, 形式化来说
f
(
S
,
T
)
≤
c
(
S
,
T
)
f(S, T) \le c(S, T)
f(S,T)≤c(S,T), 证明过程如下
性质2: 对于流网络的任意一个割和任意一个可行流都有
f
(
S
,
T
)
=
∣
f
∣
f(S, T)= |f|
f(S,T)=∣f∣
根据性质1和性质2推出 ∣ f ∣ = f ( S , T ) ≤ c ( S , T ) |f| = f(S, T) \le c(S, T) ∣f∣=f(S,T)≤c(S,T), 也就推出 ∣ f ∣ ≤ c ( S , T ) |f| \le c(S, T) ∣f∣≤c(S,T), 也就是最大流小于等于最小割
最大流最小割定理
对于某个流网络 G = ( V , E ) G = (V, E) G=(V,E), 以下三个结论相互等价
- 可行流 f f f是最大流
- f f f的残存网络中不存在增广路径
- 存在割 [ S , T ] [S, T] [S,T], 使得 c ( S , T ) = ∣ f ∣ c(S, T) = |f| c(S,T)=∣f∣
证明如下
1
1
1推
2
2
2
反证法, 存在增广路径, 由上述增广路径定理可知, 当前可行流不是最大流
3
3
3推
1
1
1
因为
∣
f
∣
≤
c
(
S
,
T
)
|f| \le c(S, T)
∣f∣≤c(S,T), 对于结论
3
3
3, 存在一个割使得
∣
f
∣
=
c
(
S
,
T
)
|f| = c(S, T)
∣f∣=c(S,T), 证明
f
f
f是否是最大流
最大流
≥
∣
f
∣
最大流 \ge |f|
最大流≥∣f∣, 又因为
∣
f
∣
=
c
(
S
,
T
)
≥
最大流
|f| = c(S, T) \ge 最大流
∣f∣=c(S,T)≥最大流, 因此
f
f
f是最大流
由结论 3 3 3得知, 最小割 ≤ c ( S , T ) = ∣ f ∣ ≤ 最大流 最小割 \le c(S, T) = |f| \le 最大流 最小割≤c(S,T)=∣f∣≤最大流, 也就有最小割小于等于最大流, 上述性质2可知, 最大流小于等于最小割, 因此最大流等于最小割
2
2
2推
3
3
3
如果当前残存网络不存在增广路径, 能否构造出一个割, 使得
c
(
S
,
T
)
=
∣
f
∣
c(S, T) = |f|
c(S,T)=∣f∣
构造一个合法的割
点集
S
S
S是在
G
f
G_f
Gf中
s
s
s沿着容量大于
0
0
0的边走, 走到的所有点, 因为不存在增广路径, 因此
s
s
s走不到
t
t
t
点集
T
T
T是
V
−
S
V - S
V−S
借助的
G
f
G_f
Gf构造的是原网络
G
G
G里的割
判断当前割的容量是否等于
∣
f
∣
|f|
∣f∣
构造的割的性质如下
- f ( x , y ) = c ( x , y ) f(x, y) = c(x, y) f(x,y)=c(x,y)
- f ( a , b ) = 0 f(a, b) = 0 f(a,b)=0
对于性质1, 如果
f
(
x
,
y
)
<
c
(
x
,
y
)
f(x, y) < c(x, y)
f(x,y)<c(x,y), 那么在残存网络中仍然会有
x
x
x连到
y
y
y的边, 也就
x
x
x能遍历到,
y
∈
S
y \in S
y∈S, 与我们构造的矛盾
对于性质2, 如果
f
(
a
,
b
)
>
0
f(a, b) > 0
f(a,b)>0, 那么在残存网络中也是能遍历到,
a
∈
S
a \in S
a∈S, 与我们构造的矛盾
∣ f ∣ = ∑ u ∈ S , v ∈ T f ( u , v ) − ∑ u ∈ T , v ∈ S f ( u , v ) |f| = \sum _{u \in S, v \in T} f(u, v) - \sum _{u \in T, v \in S} f(u, v) ∣f∣=u∈S,v∈T∑f(u,v)−u∈T,v∈S∑f(u,v)
因为没有反向边, 并且每个边的流量等于边的容量, 因此
∣ f ∣ = ∑ u ∈ S , v ∈ T c ( u , v ) = c ( S , T ) |f| = \sum _{u \in S, v \in T} c(u, v) = c(S, T) ∣f∣=u∈S,v∈T∑c(u,v)=c(S,T)
最大流最小割定理证毕
最大流算法实现
F o r d – F u l k e r s o n Ford–Fulkerson Ford–Fulkerson方法
求最大流的贪心方法, 维护残存网络, 不断的在残存网络中寻找增广路径, 将当前流变为 f + f ′ f + f' f+f′, 然后在新的流的残存网络中继续寻找增广路径, 将当前残存网络 G f G_f Gf变为 G f + f ′ G_{f + f'} Gf+f′
上图是残存网络中更新的过程,
k
k
k是路径的流量, 也就是所有边容量的最小值, 然后更新所有边的容量
E d m o n d s – K a r p Edmonds–Karp Edmonds–Karp算法求最大流
时间复杂度 O ( n m 2 ) O(nm ^ 2) O(nm2)
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
const int N = 1010, M = 20010, INF = 0x3f3f3f3f;
int n, m, s_node, t_node;
int head[N], edge_end[M], next_edge[M], w[M], edge_index;
int q[N], pre[N], min_val[N];
bool vis[N];
void add(int ver1, int ver2, int val) {
edge_end[edge_index] = ver2, next_edge[edge_index] = head[ver1], w[edge_index] = val, head[ver1] = edge_index++;
}
bool bfs() {
memset(vis, false, sizeof vis);
int h = 0, t = -1;
q[++t] = s_node;
vis[s_node] = true;
min_val[s_node] = INF;
while (h <= t) {
int u = q[h++];
for (int i = head[u]; ~i; i = next_edge[i]) {
int ver = edge_end[i];
if (!vis[ver] && w[i]) {
vis[ver] = true;
min_val[ver] = min(min_val[u], w[i]);
pre[ver] = i;
if (ver == t_node) return true;
q[++t] = ver;
}
}
}
return false;
}
int edmonds_karp() {
int res = 0;
while (bfs()) {
int val = min_val[t_node];
res += val;
for (int i = t_node; i != s_node; i = edge_end[pre[i] ^ 1]) {
w[pre[i]] -= val;
w[pre[i] ^ 1] += val;
}
}
return res;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0), cout.tie(0);
memset(head, -1, sizeof head);
cin >> n >> m >> s_node >> t_node;
while (m--) {
int u, v, w;
cin >> u >> v >> w;
add(u, v, w);
add(v, u, 0);
}
int res = edmonds_karp();
cout << res << "\n";
return 0;
}
D i n i c Dinic Dinic算法求最大流
将所有能够增广的路径全部计算, 因为可能有环, 因此使用分层图优化, 路径只能从前一层走到后一层
分层图 + 当前弧优化
时间复杂度
O
(
n
2
m
)
O(n ^ 2m)
O(n2m)
从起点到终点, 可以流
l
i
m
i
t
limit
limit的流量, 搜索从当前点开始到终点能流的流量
假设在搜索到终点后流量
f
i
<
l
i
m
i
t
f_i < limit
fi<limit, 说明
f
i
f_i
fi一定会满流, 那么在下一次搜索的时候不需要搜索该边
而是从下一条边开始搜, 代码表示为
c
u
r
r
[
u
]
=
i
curr[u] = i
curr[u]=i
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
const int N = 10010, M = 200010, INF = 0x3f3f3f3f;
int n, m, s_node, e_node;
int head[N], edge_end[M], next_edge[M], w[M], edge_index;
int q[N], layer[N], curr[N];
void add(int ver1, int ver2, int val) {
edge_end[edge_index] = ver2, next_edge[edge_index] = head[ver1], w[edge_index] = val, head[ver1] = edge_index++;
}
bool bfs() {
memset(layer, -1, sizeof layer);
int h = 0, t = -1;
q[++t] = s_node;
layer[s_node] = 0;
curr[s_node] = head[s_node];
while (h <= t) {
int u = q[h++];
for (int i = head[u]; ~i; i = next_edge[i]) {
int ver = edge_end[i];
if (layer[ver] == -1 && w[i]) {
layer[ver] = layer[u] + 1;
curr[ver] = head[ver];
if (ver == e_node) return true;
q[++t] = ver;
}
}
}
return false;
}
int dfs(int u, int limit) {
if (u == e_node) return limit;
// 从当前点向后流的流量
int flow = 0;
for (int i = curr[u]; ~i && flow < limit; i = next_edge[i]) {
int ver = edge_end[i];
// i前面的边都用完了, 当前弧更新为i
curr[u] = i;
if (layer[ver] == layer[u] + 1 && w[i]) {
int val = dfs(ver, min(w[i], limit - flow));
if (!val) layer[ver] = -1;
w[i] -= val;
w[i ^ 1] += val;
flow += val;
}
}
return flow;
}
int dinic() {
int res = 0, flow;
while (bfs()) {
// 搜索增广路径并且累计全部的流量
while ((flow = dfs(s_node, INF))) {
res += flow;
}
}
return res;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0), cout.tie(0);
memset(head, -1, sizeof head);
cin >> n >> m >> s_node >> e_node;
while (m--) {
int u, v, w;
cin >> u >> v >> w;
add(u, v, w);
add(v, u, 0);
}
int res = dinic();
cout << res << "\n";
return 0;
}