流网络: G = ( V , E ) G = (V, E) G=(V,E)
残留网络定义:一个可行流流网络 f f f 对应一个残留网络 G f G_f Gf?
重要结论:原网络的可行流 f f f 加上可行流对应的残留网络 G f G_f Gf?,也是一个可行流
在残留网络里,如果沿着容量大于 0 的边走,能走到汇点,则这条路径叫做增广路径
将点集 V 分成 S 和 T 两个子集
割的容量: c ( S , T ) = ∑ u ∈ S ∑ v ∈ T c ( u , v ) c(S, T) = \sum_{u ∈ S} \sum_{v ∈ T} c(u, v) c(S,T)=∑u∈S?∑v∈T?c(u,v)
割的流量: f ( S , T ) = ∑ u ∈ S ∑ v ∈ T f ( u , v ) ? ∑ u ∈ T ∑ v ∈ S f ( u , v ) f(S, T) = \sum_{u ∈ S} \sum_{v ∈ T} f(u, v) - \sum_{u ∈ T} \sum_{v ∈ S} f(u, v) f(S,T)=∑u∈S?∑v∈T?f(u,v)?∑u∈T?∑v∈S?f(u,v)
重要性质:
对于任意一个割,割的流量一定小于等于割的容量,即 f ( S , T ) ≤ c ( S , T ) f(S, T) \leq c(S, T) f(S,T)≤c(S,T)
割的流量等于原流网络的流量,即 f ( S , T ) = ∣ f ∣ f(S,T) = |f| f(S,T)=∣f∣
f ( X , Y ) = ? f ( Y , X ) f(X, Y) = -f(Y, X) f(X,Y)=?f(Y,X)
f ( Z , X ∪ Y ) = f ( Z , X ) + f ( Z , Y ) f(Z, X ∪ Y) = f(Z, X) + f(Z, Y) f(Z,X∪Y)=f(Z,X)+f(Z,Y)
f ( X ∪ Y , Z ) = f ( X , Z ) + f ( Y , Z ) f(X ∪ Y, Z) = f(X, Z) + f(Y, Z) f(X∪Y,Z)=f(X,Z)+f(Y,Z)
以下三个条件是等价的
维护流网络的残留网络,不断进行以下流程:
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
const int N = 1010, M = 20020, INF = 1e8;
// 邻接表存储残留网络
// 正向边和反向边成对存在,正向边的下标异或上1得到方向边的下标
int n, m, S, T;
int h[N], e[M], f[M], ne[M], idx; // f表示容量
int q[N], d[N], pre[N];
bool st[N]; // 避免重复搜索
void add(int a, int b, int c)
{
// 正向边
e[idx] = b, f[idx] = c, ne[idx] = h[a], h[a] = idx++;
// 反向边,初始容量为0
e[idx] = a, f[idx] = 0, ne[idx] = h[b], h[b] = idx++;
}
// bfs找增广路
bool bfs()
{
int hh = 0, tt = 0;
memset(st, false, sizeof(st));
q[0] = S, st[S] = true, d[S] = INF;
while(hh <= tt) {
// 从队列中弹出一个元素进行BFS
int t = q[hh++];
for(int i = h[t]; ~i; i = ne[i]) {
// 节点t的临接边i的下一节点ver
int ver = e[i];
// 没遍历过且边i的容量不为0
if( !st[ver] && f[i] ) {
st[ver] = true;
// 流到节点ver的流量为流到t的流量和边i容量的最小值
d[ver] = min(d[t], f[i]);
// 记录节点ver前驱边的编号
pre[ver] = i;
if(ver == T) return true;
// ver入队
q[++tt] = ver;
}
}
}
return false;
}
// EK 算法
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;
// 边ab的容量为c
cin >> a >> b >> c;
add(a, b, c);
}
cout << EK() << endl;
return 0;
}
O ( V E 2 ) O(VE^2) O(VE2)