通用 Dinic 最大流板子
#include <bits/stdc++.h>
using namespace std;
const int N = 10005; // 点的数量上限
const int M = 200005; // 边的数量上限(记得 *2,因为要建反向边)
const int INF = 0x3f3f3f3f;
struct Edge {
int to, cap, rev; // 终点,残量,反向边编号
};
vector<Edge> g[N];
int depth[N], cur[N];
/* 加边(正反两条) */
void add(int u, int v, int cap) {
g[u].push_back({v, cap, (int)g[v].size()});
g[v].push_back({u, 0, (int)g[u].size() - 1});
}
/* BFS 建层 */
bool bfs(int s, int t) {
fill(depth, depth + N, -1);
queue<int> q;
depth[s] = 0;
q.push(s);
while (q.size()) {
int u = q.front(); q.pop();
for (auto &e : g[u]) { // 注意这里的 &
if (e.cap > 0 && depth[e.to] == -1) {
depth[e.to] = depth[u] + 1;
q.push(e.to);
}
}
}
return depth[t] != -1;
}
/* DFS 多路增广 */
int dfs(int u, int t, int f) {
if (u == t) return f;
for (int &i = cur[u]; i < g[u].size(); i++) {
Edge &e = g[u][i]; // 关键:引用,直接改原图
if (e.cap > 0 && depth[e.to] == depth[u] + 1) {
int flow = dfs(e.to, t, min(f, e.cap));
if (flow > 0) {
e.cap -= flow; // 正向边减少
g[e.to][e.rev].cap += flow; // 反向边增加
return flow;
}
}
}
return 0;
}
/* Dinic 主函数 */
int dinic(int s, int t) {
int flow = 0;
while (bfs(s, t)) {
fill(cur, cur + N, 0);
while (int f = dfs(s, t, INF)) {
flow += f;
}
}
return flow;
}
用法示例
例题:P3376【模板】网络最大流)(洛谷)
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n, m, s, t;
cin >> n >> m >> s >> t;
for (int i = 0; i < m; i++) {
int u, v, w;
cin >> u >> v >> w;
add(u, v, w);
}
cout << dinic(s, t) << "\n";
return 0;
}
板子记忆
建图 add,分层 bfs,送水 dfs,主循环 dinic
关键三处 &,少一个都可能 WA:
| 位置 | 为什么 |
|---|---|
for (auto &e : g[u]) |
防止 BFS 误拷贝 |
int &i = cur[u] |
当前弧优化 |
Edge &e = g[u][i] |
核心!改原图残量 |
注意事项
-
N 开够
- 点编号从 1 开始也要保证
N > max_vertex
- 点编号从 1 开始也要保证
-
M 要 ×2
- 因为每条边要建一条反向边
-
多测记得清空
for (int i = 1; i <= n; i++) g[i].clear(); -
INF 不要开太大
0x3f3f3f3f足够,且不容易溢出
讲解
一、怎么存图?(残量网络)
边的结构
struct Edge {
int to; // 这条边指向谁
int cap; // 剩余容量(残量)
int rev; // 反向边在 to 的邻接表中的下标
};
这是什么意思?
网络流必须支持**"反悔"**。
比如你给 u→v流了 5 单位,以后你可能想退回去一点。
所以我们每条边都配一条反向边:
-
正向边:
cap = 剩余容量 -
反向边:
cap = 已流流量
rev的作用是:快速找到反向边在哪。
邻接表存图
vector<Edge> g[N];
g[u]里存的是 u 的所有出边。
加边函数(最重要)
void add(int u, int v, int cap) {
g[u].push_back({v, cap, (int)g[v].size()}); // 正向边
g[v].push_back({u, 0, (int)g[u].size() - 1}); // 反向边
}
逐行解释:
g[u].push_back({v, cap, (int)g[v].size()});
-
从
u到v,容量是cap -
反向边在
g[v]的第g[v].size()个位置g[v].push_back({u, 0, (int)g[u].size() - 1});
-
反向边容量为 0(一开始没流)
-
反向边的
rev指向刚刚加的正向边
以后只要 e.cap -= f,同时 g[e.to][e.rev].cap += f,就完成了"反悔"
二、BFS:给图"分层"
int depth[N];
bool bfs(int s, int t) {
fill(depth, depth + N, -1);
queue<int> q;
depth[s] = 0;
q.push(s);
while (q.size()) {
int u = q.front(); q.pop();
for (auto &e : g[u]) {
if (e.cap > 0 && depth[e.to] == -1) {
depth[e.to] = depth[u] + 1;
q.push(e.to);
}
}
}
return depth[t] != -1;
}
这段代码在干嘛?
-
depth[v]:从源点 s 到 v 的最短距离(按边数) -
depth[v] == -1:表示这个点还没访问到 -
e.cap > 0:只走还能流的边
如果 depth[t] == -1,说明 s 到 t 已经不连通了,算法结束
三、DFS:在分层图上"送水"
当前弧数组
int cur[N];
cur[u]记录:u 这个节点,我已经尝试到第几条边了
DFS 主体
int dfs(int u, int t, int f) {
if (u == t) return f;
for (int &i = cur[u]; i < g[u].size(); i++) {
Edge &e = g[u][i];
if (e.cap > 0 && depth[e.to] == depth[u] + 1) {
int ret = dfs(e.to, t, min(f, e.cap));
if (ret > 0) {
e.cap -= ret;
g[e.to][e.rev].cap += ret;
return ret;
}
}
}
return 0;
}
一行一行拆:
if (u == t) return f;
-
到了终点,直接返回能送的水量
for (int &i = cur[u]; i < g[u].size(); i++)
-
关键!
-
cur[u]是引用,修改它会直接保存进度 -
下次再来 u,不会从头扫边
if (e.cap > 0 && depth[e.to] == depth[u] + 1)
-
只走还有容量
-
并且严格向下一层走(防止绕圈)
int ret = dfs(e.to, t, min(f, e.cap));
-
能送的水 = 当前限制
f和 边容量e.cap的最小值if (ret > 0) {
e.cap -= ret; // 正向边减
g[e.to][e.rev].cap += ret; // 反向边加
return ret;
} -
完成一次增广,立刻返回
-
一次 DFS 只送一条增广路
-
但因为有
while (dfs(...)),会反复调用,直到送不动
四、Dinic 主函数:把一切串起来
int dinic(int s, int t) {
int flow = 0;
while (bfs(s, t)) { // 只要还能分层
fill(cur, cur + N, 0); // 每一轮重置当前弧
while (int f = dfs(s, t, INF)) {
flow += f; // 累加流量
}
}
return flow;
}
这里发生了什么?
-
BFS 建层
-
DFS 多路增广(阻塞流)
-
重复直到无法到达 t
📌 每一轮 BFS 后,dfs会被反复调用,直到当前分层图被"堵死"。
五、一张"对照表":代码 vs 现实
| 代码 | 现实含义 |
|---|---|
Edge::cap |
水管还能流多少 |
Edge::rev |
反悔用的反向水管 |
depth[] |
城市在第几站 |
bfs |
画地图、编号 |
dfs |
送水工出发 |
cur[] |
送水工的小本子 |
while(dfs) |
把这一层水送干 |