网络流
(暂时还没有写完)
网络流属于图论的一部分,一般OI上涉及到的网络流的部分只有最大流和费用流。
前置知识
有向图 \(G = (V,E)\) 就叫做一个网络,每条有向边有一个属性 $c(x, y) $ 叫做边 \((x, y)\) 的容量,即这条边最多所能容纳的流量,一般来说在一个网络中还有两个特殊的点,源点 \(s\) 和 汇点 \(t\)。
网络流中有一个非常重要的函数 \(f(x, y)\),它表示流经边 \((x, y)\) 的流量数值,因此它叫做网络的流函数。
流函数的三大性质
- 容量限制
即流量不能大于容量,即 \(f(x, y) <= c(x, y)\)。 - 斜对称
虽然我不知道为啥叫做斜对称,简单的来说,我们对于一条有向边 \((x, y)\),总是有一条它的反向边 \((y, x)\),并且如果边 \((x, y)\) 的流量为 \(flow\),那么反向边 \((y, x)\) 的流量为 \(-flow\)。
原因:其实加反向边的过程就相当于你有了反悔的机会,让网络流的正确性得到保证,当然初学的话只需要记住别忘了加反向边就可以。
- 流量守恒
通俗的说就是源点流出的流量等于汇点接入的流量,即除了源点与汇点,别的点不产生流量,它们只是流量的搬运工。
偷 rvalue 学长的一句话:想象一个不可压缩的流体的运输管网, 每个管道都有防倒流阀门 (保证有向) , 每个管道还有一个单位时间内的流量限制, 那就是一个网络流模型的样子了。
最大流
通俗的说就是源点到汇点的最大流量。
求最大流的方法有很多,这里只讲 EK 和 Dinic 两种方法,而且一般来说最大流都是写 Dinic,讲 EK 只是为费用流做基础。
EK 增广路算法
首先定义剩余容量为边的容量减去当前的流量的剩下的值(感性理解)。
定义一条增广路就是从 \(s\) 到 \(t\) 的一条路径上每条边的剩余容量都大于 0 的路径。
很显然增广路是可以对最大流做出贡献的,那么算法的流程已经出来了,每次找增广路,然后更新就可以了。
对于斜对称的处理:显然如果一条边的剩余容量减少 \(x\),那么它的反向边的剩余容量就应该增加 \(x\),所以我们可以利用成对变换的方法,边表的 \(cnt\) 从 \(2\) 开始存就可以了。
网络流不大需要分析复杂度,因为一般是非常跑不满的,但是 EK 算法的极限复杂度是 \(O(nm^2)\),但是在实际问题上跑个几千个点的数据一般没有什么问题(边与点同阶)。
放个代码吧:
代码
#include <bits/stdc++.h>
#define ll long long
const int N = 205, M = 1e4 + 5;
inline int read(int x = 0) { return scanf("%d", &x), x; }
ll maxflow;//最大流
int n, m, s, t, head[N], cnt = 1, vis[N], pre[N], incf[N], q[N];
struct edge { int to, next, w; } e[M];
inline void add(int x, int y, int z) {
e[++cnt] = (edge){y, head[x], z}, head[x] = cnt;
}
bool bfs() {//寻找增广路
memset(vis, 0, sizeof(vis));
int l = 1, r = 0;
q[++r] = s, vis[s] = 1, incf[s] = 0x3f3f3f3f;
while(l <= r) {
int u = q[l++];
for(register int i = head[u], v; i; i = e[i].next) {
if(vis[v=e[i].to] || e[i].w <= 0) continue;
incf[v] = std::min(incf[u], e[i].w);
pre[v] = i;//pre记录这条边的标号,便于找到具体方案
q[++r] = v, vis[v] = 1;
if(v == t) return 1;
}
}
return 0;
}
void update() {//更新操作
int u = t;
while(u != s) {
int i = pre[u];
e[i].w -= incf[t], e[i^1].w += incf[t];
u = e[i^1].to;
}
maxflow += incf[t];
}
int main() {
n = read(), m = read(), s = read(), t = read();
for(register int i = 1, x, y, z; i <= m; ++i)
x = read(), y = read(), z = read(), add(x, y, z), add(y, x, 0);//建双向边,且反向边的容量设成0
while(bfs()) update();
printf("%lld\n", maxflow);
return 0;
}
Dinic 算法
Dinic算法才是用的最多的。
我们发现 EK 算法每次只是更新一条增广路,严重增大了它的复杂度,而 Dinic 算法是一次可以更新多条增广路,即多路增广。

浙公网安备 33010602011771号