网络流学习笔记

网络流学习笔记

:笔者比较菜,许多证明没有给出,一些地方可能有误。

一些定义

网络

对于一张有向图 G(V,E)G \in (V, E),有一个源点 SVS \in V 和一个汇点 TVT \in V,每一条边 (u,v)E(u, v) \in E 有一个限流 c(u,v)c(u, v)。特别的,(u,v)E , c(u,v)=0\forall (u, v) \notin E \ , \ c(u, v) = 0

网络流

对于一个函数 f(u,v)f(u, v),若满足如下性质:

  • f(u,v)c(u,v)f(u, v) \le c(u, v),即流量小于限流;
  • f(u,v)=f(v,u)f(u, v) = -f(v, u),反边的流量等于这条边流量的相反数,为什么要有反边我们在下面解释;
  • xV{S, T}, (u,x)Ef(u,x)=(x,u)Ef(x,u)\forall x \in V - \{ S, \ T\}, \ \sum _{(u, x) \in E} f(u,x) = \sum_{(x, u) \in E}f(x, u),从源点流出的流量等于流入汇点的流量;

则函数 f(u,v)f(u, v) 被称为流函数。

最大流

求从源点出发,能够流到汇点的最大流量问题,称为最大流。

在了解求解算法之前,我们需要了解一些概念。

残量网络

c(u,v)c(u, v) 在减去 f(u,v)f(u, v) 后得到的剩余流量 c(u,v)c'(u, v) 组成的网络被称为残量网络。这个网络可以比原图少一些边(c(x,y)0, c(x,y)=0c(x, y) \neq 0, \ c'(x, y) = 0),也可以有一些原图没有的边(反边)。

增广路

在残量网络的一条从 SSTT 所有边剩余流量大于零的路径称为增广路。因为所有边剩余流量大于零,所以可以在增广路上在增加一些流量使得流更大,所以最大流的残量网络一定没有增广路

EK (Edmonds-Karp) 算法

EK\tt{EK}​ 算法就是基于增广路的。算法的思想是每次 bfs\tt{bfs}​ 找到一条增广路,并更新边权造出残量网络,更新答案,直到图中没有增广路。这里解释一下为什么要有反边,因为当前找到的增广路不一定是最优的,可能不包含在最大流的路径中,直接做就会出现错误。而反边给了我们反悔的机会,走反边就相当于减少了原来走这条边的流量,从而保证算法的正确性。

具体的,我们在残量网络上从源点 SS 开始 bfs\tt{bfs} ,每次走一直到汇点 TT 停止,记录路径上的的边的流量的最小值 vv 这样,就找到了一条增广路。接着重新走一遍增广路,每一条边的流量减去 vv ,反边流量加上 vv ,最后答案也加上 vv,这样就找到了最大流。

TIP

如果你使用邻接表存图,那么有一个小技巧找到反边:我们都知道,22 异或 113333 异或 1122,因此可以用异或一的方法快速找到反边。

EK\tt{EK} 的时间复杂度是 O(nm2)O(nm^2),实际较快,能跑 10310^3 ~ 10410^4 的数据。

Dinic 算法

EK\tt{EK} 一次只找到了一条增广路,存在较大的浪费,完全可以一次找到多条,就有了 Dinic\tt{Dinic}

Dinic\tt{Dinic} 的算法流程是这样的:

  • 通过 bfs\tt{bfs} 从源点 SS 对原图进行分层,构造分层图,直到汇点 TT
  • 从源点 SS 开始 dfs\tt{dfs},每次只走到层数比当前层数多一的点,找到当前分层图中的所有增广路并更新。

为什么要分层?通过分层可以找到最短路,最短路就可以避免一些问题,像这个图:

最短路就不会搞中间这条边了。以上纯属我口胡,我也不知道对不对。

实现中还有一些必要的优化,可以看下面的代码。经过优化的 Dinic\tt{Dinic} 时间复杂度是 Θ(n2m)\Theta(n^2m),实际跑时则更快,可以过 10410^4 ~ 10510^5 的数据。

CODE

EK\tt{EK} 比较菜我就不放了 (〃´-ω・) 其实是懒得写,主要还是用 Dinic\tt{Dinic}

#include <bits/stdc++.h>
using namespace std;

inline int read() {
	int x = 0, f = 0; char c = 0;
	while (!isdigit(c)) f |= c == '-', c = getchar();
	while (isdigit(c)) x = (x << 3) + (x << 1) + (c ^ 48), c = getchar();
	return f ? -x : x;
}

#define N 210
#define M 5010
#define INF (1e9)
#define int long long
typedef int Array[N];

int n, m, S, T, Mx = 0, tot = 1;
Array hd, pre, flw, dep;

struct Edge {
	int y, v, to;
	Edge(int y = 0, int v = 0, int to = 0) :
		y(y), v(v), to(to) {}
}e[M * 2];

void addE(int x, int y, int v) {
	e[++ tot] = Edge(y, v, hd[x]), hd[x] = tot;
}

int bfs() { // bfs 求出分层
	queue<int> q;
	memset(dep, -1, sizeof dep);
	q.push(S),dep[S] = 0;
	while (q.size()) {
		int x = q.front(); q.pop();
		for (int i = hd[x]; i; i = e[i].to) {
			int y = e[i].y, v = e[i].v;
			if (v == 0 || dep[y] >= 0) continue;
			dep[y] = dep[x] + 1, q.push(y);
			if (y == T) return 1;
		}
	}
	return 0;
}

int dfs(int x, int in, int out = 0) {
	// in : 剩余流入的流量, out : 流出的流量
	if (x == T) return in; // 如果到达汇点直接返回
	for (int i = hd[x]; i; i = e[i].to){
		int y = e[i].y, v = e[i].v;
		if (dep[y] != dep[x] + 1 || v == 0) continue;
		// 如果 y 不是下一层或者无法流出就不用做了
		v = dfs(y, min(in, v));
		// 算出下面可以流到汇点的流量
		e[i].v -= v, e[i ^ 1].v += v;
		in -= v, out += v;
		if (!in) break; // 如果流量用完了就不用做了
	}
	if (!out) dep[x] = -1; // 流量用完了这个点就不用做了
	return out;
}

signed main() {
	n = read(), m = read(), S = read(), T = read();
	for (int i = 1; i <= m; i ++) {
		int x = read(), y = read(), v = read();
		addE(x, y, v), addE(y, x, 0);
	}

	while (bfs()) Mx+=dfs(S,1e9);

	printf("%lld\n", Mx);
	return 0;
}

这也是 模板题 AC 代码。

最大流求二分图最大匹配

源点 SS 连向左边的点,右边的点连向汇点 TT,二分图无向边改为左边连向右边的边,所有边边权都为一,这样就把二分图最大匹配转换成了最大流。求方案可以遍历中间的边,若剩余流量为 00 说明这条边被用了。

特别的,用 Dinic\tt{Dinic} 求二分图最大匹配复杂度为 Θ(nm)\Theta(\sqrt nm)

CODE

给出建图部分以及求方案部分的代码。

// 建边
for (int i = 1; i <= m; i ++) {
	addE(S, i, 1);
}
for (int j = m + 1; j <= n; j ++) {
	addE(j, T, 1);
}
while (true) {
	int x = read(), y = read();
	if (x < 0 && y < 0) break;
	addE(x, y, 1);
}
// 求方案
for (int i = 2; i <= tot; i ++) {
	if (e[i].y == S || e[i ^ 1].y == S) continue;
	if (e[i].y == T || e[i ^ 1].y == T) continue;
	if (e[i].v == 0 && e[i].y > m) {
		printf("%d %d\n", e[i ^ 1].y, e[i].y);
	}
}

最小割

删去一些边,使得源点和汇点不连通,求删去的边流量之和最小的问题称为最小割。

结论:最小割 = 最大流,因此最小割代码和最大流一模一样。

参考:《算法竞赛进阶指南》,OI Wiki


网络流学习笔记
https://ybwa.github.io/p/b98db1c/
作者
yb
发布于
2021年11月1日
许可协议