算法学习笔记(20):网络流
网络流
主要分为以下三大问题:
- 网络最大流问题
- 网络最小割问题
- 最小费用最大流问题
现在考得更难的还有模拟网络流, 运用网络流思想, 来解决一些神秘问题。
算法
接下来我会讲解一些通过找增广路找最大流的算法, 也就是Dinic和EK算法。
最大流最小割定理
最大流最小割定理: 对于任意网络 \(G = (V, E)\),其上的最大流 \(f\) 和最小割 \(\{S, T\}\) 总是满足 \(|f| = ||S, T||\)。
证明详见OI-WIKI, 这里感性理解, 对于一条路径, 其最大流 = 最小边, 同理最小割 = 最小边, 推广到整个图的所有路径, 所以一个网络的最大流等于最小割。
通过这个定理我们解决一些问题就可以从最小割的角度去思考。
EK算法
首先对于网络上的每一条边, 建一条反边用来退流, 为了反悔贪心, 具体理解建议手模体会。
然后寻找增广路的过程就是BFS,
- 每次从源点 \(s\) 开始BFS寻找增广路, 直到汇点 \(t\), 然后反边退流。
- 重复上述过程, 知道找不到增广路为止。
实现过程中需要记录一个 \(pre\), 将路径上的流都修改了。
根据网络上各路大佬证明可知, 增广轮数上界为 \(O(|V||E|)\), 所以EK算法时间复杂度 \(O(|V||E|^2)\), 不过一般跑不满, \(10^3 - 10^4\)的数据一般都跑得过。
代码
点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N = 1205;
const int M = 120005;
const int INF = 1e9;
int n, m, s, t, head[N], tot = 1;
struct Edge{
int to, next, dis;
}edge[M << 1];
void add(int x, int y, int z) {
edge[++tot] = (Edge){y, head[x], z}; head[x] = tot;
edge[++tot] = (Edge){x, head[y], 0}; head[y] = tot;
}
int pre[N], flow[N];
bool bfs(int s, int t) {
memset(pre, -1, sizeof pre);
queue<int> q; q.push(s);
flow[s] = INF;
while (!q.empty()) {
int u = q.front(); q.pop();
if (u == t) break;
for (int i = head[u]; i; i = edge[i].next) {
int v = edge[i].to;
if (pre[v] == -1 && edge[i].dis) {
pre[v] = i;
flow[v] = min(edge[i].dis, flow[u]);
q.push(v);
}
}
}
return (pre[t] != -1);
}
int EK(int s, int t) {
int res = 0;
while (bfs(s, t)) {
for (int i = t; i != s; i = edge[pre[i] ^ 1].to) {
edge[pre[i]].dis -= flow[t];
edge[pre[i] ^ 1].dis += flow[t];
}
res += flow[t];
}
return res;
}
signed main() {
scanf("%lld%lld%lld%lld", &n, &m, &s, &t);
for (int i = 1, u, v, w; i <= m; i++) scanf("%lld%lld%lld", &u, &v, &w), add(u, v, w);
printf("%lld\n", EK(s, t));
return 0;
}
Dinic算法
考虑EK算法每次BFS只找一条增广路需要遍历整个网络, 有点浪费, 我们可以建立分层图进行增广, 其中需要用到当前弧优化和多路增广优化, 注意当前弧优化师Dinic算法的一部分, 多路增广是常数优化。 Dinic算法非常优秀, 时间复杂度 \(O(|V|^2|E|)\), 一般可以跑 \(10^4-10^5\) 规模的数据。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N = 205;
const int M = 5005;
const int INF = 1e9;
int n, m, s, t, head[N], tot = 1;
struct Edge{
int to, next, dis;
}edge[M << 1];
void add(int x, int y, int z) {
edge[++tot] = (Edge){y, head[x], z}; head[x] = tot;
edge[++tot] = (Edge){x, head[y], 0}; head[y] = tot;
}
int d[N], now[N];
bool bfs() {
queue<int> q;
memset(d, 0, sizeof d);
d[s] = 1, now[s] = head[s], q.push(s);
while (!q.empty()) {
int u = q.front(); q.pop();
for (int i = head[u]; i; i = edge[i].next) {
int v = edge[i].to;
if (edge[i].dis && !d[v]) {
d[v] = d[u] + 1;
now[v] = head[v];
q.push(v);
if (v == t) return 1;
}
}
}
return 0;
}
int dinic(int u, int flow) {
if (u == t) return flow;
int rest = flow;
for (int i = now[u]; i && rest; i = edge[i].next) {
now[u] = i;//注意这里, 可以在 for 循环里的 i 前加地址符, 但是就要把 &&rest 提出来写在后面
int v = edge[i].to;
if (edge[i].dis && d[v] == d[u] + 1) {
int k = dinic(v, min(rest, edge[i].dis));
if (!k) d[v] = 0;
rest -= k;
edge[i].dis -= k;
edge[i ^ 1].dis += k;
}
}
return flow - rest;
}
int sum;
signed main() {
scanf("%lld%lld%lld%lld", &n, &m, &s, &t);
for (int i = 1, u, v, w; i <= m; i++) scanf("%lld%lld%lld", &u, &v, &w), add(u, v, w);
while (bfs()) sum += dinic(s, INF);
printf("%lld\n", sum);
return 0;
}