网络流初步
0. 基本定义
一个网络
函数
- 容量限制:
。 - 斜对称性:
。 - 流量守恒:
。
1. 最大流
最大流:网络的最大流量。
1.1 EK 增广路算法
增广路:一条从源点到汇点的所有边的剩余容量
残留网:由网络中所有结点和剩余容量大于
包括有向边和其反向边。
建图时每条有向边
构建反向边的目的是提供一个“退流管道”,一旦前面的增广路堵死可行流可以通过“退流管道”退流,提供了“后悔机制”。
EK 算法通过 BFS 找到增广路并不断更新流量计算最大流。
算法流程:
- BFS 计算出一条从
的增广路,并更新 。 - 通过增广路更新增广路上的流量限制,并不断累加。
时间复杂度
点击查看代码
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <queue>
using namespace std;
typedef long long ll;
const int N = 210, M = 10010, INF = 2e9;
int n, m, S, T;
int idx = 1, e[M], h[N], ne[M]; ll c[M];
int pre[N]; ll f[M]; //pre[u] 表示u的前驱边
void add(int u, int v, int w) {
e[++ idx] = v, ne[idx] = h[u], c[idx] = w, h[u] = idx;
}
bool bfs() { // 求增广路
memset(f, 0, sizeof f), memset(pre, 0, sizeof pre), f[S] = INF;
queue<int> q; q.push(S);
while (!q.empty()) {
int u = q.front(); q.pop();
for (int i = h[u]; i != -1; i = ne[i]) {
int v = e[i];
if (!f[v] && c[i] > 0) {
f[v] = min(f[u], c[i]), pre[v] = i;
q.push(v);
if (v == T) return 1;
}
}
}
return 0;
}
ll EK() {
ll flow = 0;
while (bfs()) {
int v = T;
while (v != S) {
int i = pre[v];
c[i] -= f[T], c[i^1] += f[T], v = e[i^1];
}
flow += (ll)f[T];
}
return flow;
}
int main() {
memset(h, -1, sizeof h);
scanf("%d%d%d%d", &n, &m, &S, &T);
int u, v, w;
for (int i = 1; i <= m; ++i) {
scanf("%d%d%d", &u, &v, &w);
add(u, v, w), add(v, u, 0); // 建一条权值为0的反向边,可以反悔
}
ll flow = EK();
printf("%lld\n", flow);
return 0;
}
1.2 Dinic 算法
注意到 EK 算法中一次 BFS 只能计算一条增广路,而 Dinic 算法一次可以更新多条增广路。
令
算法流程:
- BFS 对点分层,找出增广路;
- DFS 多路增广:
- 搜索顺序优化(分层限制)
- 当前弧优化
- 剩余流量优化
- 残枝优化
- Dinic 累加可行流。
点击查看代码
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <queue>
using namespace std;
typedef long long ll;
const int N = 210, M = 10010, INF = 2e9;
int n, m, S, T;
int idx = 1, e[M], ne[M], h[N]; ll c[M];
int d[N], cur[N]; // 当前弧
void add(int u, int v, int w) {
e[++ idx] = v, ne[idx] = h[u], c[idx] = w, h[u] = idx;
}
bool bfs() {
memset(d, 0, sizeof d);
queue<int> q; q.push(S), d[S] = 1, cur[S] = h[S];
while (!q.empty()) {
int u = q.front(); q.pop();
for (int i = h[u]; i != -1; i = ne[i]) {
int v = e[i];
if (!d[v] && c[i] > 0) {
d[v] = d[u] + 1, q.push(v), cur[v] = h[v];
if (v == T) return 1;
}
}
}
return 0;
}
ll dfs(int u, ll mf) {
if (u == T) return mf;
ll sum = 0;
for (int i = cur[u]; i != -1; i = ne[i]) {
cur[u] = i; // 当前弧优化
int v = e[i];
if (d[v] == d[u]+1 && c[i] > 0) {
ll f = dfs(v, min(mf, c[i]));
c[i] -= f, c[i^1] += f, sum += f, mf -= f;
if (!mf) break; // 剩余流量优化
}
}
if (!sum) d[u] = 0; // 残枝优化
return sum;
}
ll dinic() {
ll flow = 0;
while (bfs()) flow += dfs(S, INF);
return flow;
}
int main() {
memset(h, -1, sizeof h);
scanf("%d%d%d%d", &n, &m, &S, &T);
for (int i = 1; i <= m; ++i) {
int u, v, w; scanf("%d%d%d", &u, &v, &w);
add(u, v, w), add(v, u, 0);
}
ll flow = dinic();
printf("%lld\n", flow);
return 0;
}
2. 最小割
若一个边集
最大流最小割定理:任何一个网络
证明:
假设最小割
如果我们求出了最大流
考虑如何计算最小割的最小边数,有两种方法:
- 将边权改为
,此时由于最小割边数不可能超过 ,所以此时最小割 即为最小割的最小边数。 - 将满流的边设为
,未满流的边设为 ,此时的最小割即为原最小割的最小边数。
P1344 [USACO4.4] 追查坏牛奶 Pollutant Control
点击查看代码
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <queue>
using namespace std;
typedef long long ll;
const int N = 35, M = 2e3+10, INF = 2e9;
int n, m;
int idx = 1, e[M], ne[M], h[N], c[M];
int pre[N], f[N];
void add(int u, int v, int w) {
e[++ idx] = v, ne[idx] = h[u], c[idx] = w, h[u] = idx;
}
bool bfs() {
memset(f, 0, sizeof f), memset(pre, 0, sizeof pre), f[1] = INF;
queue<int> q; q.push(1);
while (q.size()) {
int u = q.front(); q.pop();
for (int i = h[u]; i != -1; i = ne[i]) {
int v = e[i];
if (!f[v] && c[i]) {
f[v] = min(f[u], c[i]), pre[v] = i;
q.push(v);
if (v == n) return 1;
}
}
}
return 0;
}
ll EK() {
ll flow = 0;
while (bfs()) {
int v = n;
while (v != 1) {
int i = pre[v];
c[i] -= f[n], c[i^1] += f[n], v = e[i^1];
}
flow += (ll)f[n];
}
return flow;
}
int main() {
memset(h, -1, sizeof h);
scanf("%d%d", &n, &m);
for (int i = 1; i <= m; ++i) {
int u, v, w; scanf("%d%d%d", &u, &v, &w);
add(u, v, w), add(v, u, 0);
}
ll max_flow = EK();
printf("%lld ", max_flow);
for (int i = 2; i <= m*2; i += 2) {
if (!c[i]) c[i] = 1; else c[i] = INF;
c[i^1] = 0;
}
ll min_cut = EK();
printf("%lld ", min_cut);
return 0;
}
本文作者:Jasper08
本文链接:https://www.cnblogs.com/Jasper08/p/17529527.html
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步