基本概念

流网络

在这里插入图片描述
对于一个有向图, 抽象成水管里的水的模型, 每根管子有容量限制, 计为G=(V,E)G = (V, E)G=(V,E), 首先不考虑反向边

在这里插入图片描述
对于任意无向图, 都可以将反向边转化为上述形式

如果一条边不存在, 定义为容量为000, 形式上来说就是c(u,v)=0c(u, v) = 0c(u,v)=0

可行流

对于每一个流网络考虑一个可行流fff, 指定每条边的流量, 需要满足两个条件

  • 容量限制 0≤f(u,v)≤c(u,v)0 \le f(u, v) \le c(u, v)0f(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)

最大流指的是流量值最大的可行流

残存网络

残存网络是对于流网络某一个可行流
在这里插入图片描述
对于某个可行流f1f_1f1, 残存网络Gf1G_{f_1}Gf1
残存网络的点集与原图完全一致Vf=VV_f = VVf=V, 但是边集不仅包含原图的边还包含原图的反向边Ef=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)

对于任意一个可行流fff, 都可以求出残存网络GfG_{f}Gf

记残存网络的可行流f′f'f, f+f′f + f'f+f也是原来流网络GGG的一个可行流

新的流的流量值等于两个流的流量值之和 ∣f+f′∣=∣f∣+∣f′∣|f + f'| = |f| + |f'|f+f=f+f, 流量相加等同于每条边相加

为什么新的流是原流网络GGG的可行流?

  • 是否满足容量限制
  • 是否满足流量守恒

增广路径

残存网络中, 从源点出发沿着**容量大于000**的点走如果能走到汇点, 那么这一条路径就是增广路径
在这里插入图片描述

上述红色路径就是增广路径, 增广路径一定是一个可行流, 流量大于000
如果GfG_fGf总不存在增广路径, 断言fff是原来流网络的最大流

将点集VVV分为两个集合S,TS, TS,T, 满足以下条件

  • s∈Ss \in SsS, t∈Tt \in TtT
  • S∪T=VS \cup T = VST=V, S∩T=∅S \cap T = \emptysetST=

在这里插入图片描述
上述就是割的形式化描述

割的容量

在这里插入图片描述
所有从SSS指向TTT的边, 被称为割的容量, 容量不考虑回来的边

c(S,T)=∑u∈S,v∈Tc(u,v) c(S, T) = \sum _{u\in S, v \in T} c(u, v) c(S,T)=uS,vTc(u,v)

割的流量

所有从SSS留到TTT的流量减去从TTT流向SSS的流量, 也就是净流量

f(S,T)=∑u∈S,v∈Tf(u,v)−∑u∈T,v∈Sf(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)=uS,vTf(u,v)uT,vSf(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)fc(S,T), 也就是最大流小于等于最小割

最大流最小割定理

对于某个流网络G=(V,E)G = (V, E)G=(V,E), 以下三个结论相互等价

  1. 可行流fff是最大流
  2. fff的残存网络中不存在增广路径
  3. 存在割[S,T][S, T][S,T], 使得c(S,T)=∣f∣c(S, T) = |f|c(S,T)=f

证明如下

111222
反证法, 存在增广路径, 由上述增广路径定理可知, 当前可行流不是最大流

333111
因为∣f∣≤c(S,T)|f| \le c(S, T)fc(S,T), 对于结论333, 存在一个割使得∣f∣=c(S,T)|f| = c(S, T)f=c(S,T), 证明fff是否是最大流
最大流≥∣f∣最大流 \ge |f|最大流f, 又因为∣f∣=c(S,T)≥最大流|f| = c(S, T) \ge 最大流f=c(S,T)最大流, 因此fff是最大流

由结论333得知, 最小割≤c(S,T)=∣f∣≤最大流最小割 \le c(S, T) = |f| \le 最大流最小割c(S,T)=f最大流, 也就有最小割小于等于最大流, 上述性质2可知, 最大流小于等于最小割, 因此最大流等于最小割

222333
如果当前残存网络不存在增广路径, 能否构造出一个, 使得c(S,T)=∣f∣c(S, T) = |f|c(S,T)=f
构造一个合法的割
点集SSS是在GfG_fGfsss沿着容量大于000的边走, 走到的所有点, 因为不存在增广路径, 因此sss走不到ttt
点集TTTV−SV - SVS
借助的GfG_fGf构造的是原网络GGG里的割

判断当前割的容量是否等于∣f∣|f|f
在这里插入图片描述
构造的割的性质如下

  1. f(x,y)=c(x,y)f(x, y) = c(x, y)f(x,y)=c(x,y)
  2. f(a,b)=0f(a, b) = 0f(a,b)=0

对于性质1, 如果f(x,y)<c(x,y)f(x, y) < c(x, y)f(x,y)<c(x,y), 那么在残存网络中仍然会有xxx连到yyy的边, 也就xxx能遍历到, y∈Sy \in SyS, 与我们构造的矛盾
对于性质2, 如果f(a,b)>0f(a, b) > 0f(a,b)>0, 那么在残存网络中也是能遍历到, a∈Sa \in SaS, 与我们构造的矛盾

∣f∣=∑u∈S,v∈Tf(u,v)−∑u∈T,v∈Sf(u,v) |f| = \sum _{u \in S, v \in T} f(u, v) - \sum _{u \in T, v \in S} f(u, v) f=uS,vTf(u,v)uT,vSf(u,v)

因为没有反向边, 并且每个边的流量等于边的容量, 因此

∣f∣=∑u∈S,v∈Tc(u,v)=c(S,T) |f| = \sum _{u \in S, v \in T} c(u, v) = c(S, T) f=uS,vTc(u,v)=c(S,T)

最大流最小割定理证毕

最大流算法实现

Ford–FulkersonFord–FulkersonFordFulkerson方法

求最大流的贪心方法, 维护残存网络, 不断的在残存网络中寻找增广路径, 将当前流变为f+f′f + f'f+f, 然后在新的流的残存网络中继续寻找增广路径, 将当前残存网络GfG_fGf变为Gf+f′G_{f + f'}Gf+f

在这里插入图片描述
上图是残存网络中更新的过程, kkk是路径的流量, 也就是所有边容量的最小值, 然后更新所有边的容量

Edmonds–KarpEdmonds–KarpEdmondsKarp算法求最大流

时间复杂度O(nm2)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;
}


DinicDinicDinic算法求最大流

将所有能够增广的路径全部计算, 因为可能有环, 因此使用分层图优化, 路径只能从前一层走到后一层
分层图 + 当前弧优化
时间复杂度O(n2m)O(n ^ 2m)O(n2m)

在这里插入图片描述
从起点到终点, 可以流limitlimitlimit的流量, 搜索从当前点开始到终点能流的流量

在这里插入图片描述
假设在搜索到终点后流量fi<limitf_i < limitfi<limit, 说明fif_ifi一定会满流, 那么在下一次搜索的时候不需要搜索该边

在这里插入图片描述
而是从下一条边开始搜, 代码表示为curr[u]=icurr[u] = icurr[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;
}