网络流(Dinic)
💡 核心思想
网络流是在有向图中求最大流或最小费用流的算法族。Dinic 算法是其中效率较高的算法:通过 BFS 构建分层图(按到源点的距离分层),然后用 DFS 在分层图上寻找阻塞流。关键技巧是当前弧优化——记录每个点上次搜索到哪条边,避免重复遍历。时间复杂度 O(n²m),在单位容量网络中为 O(m√n)。
网络流是 NOI 级别的算法,CSP-S 很少直接考,但掌握它对你理解图论的上限很有帮助。Dinic 的代码比较长,建议比赛前背熟模板。关键概念:残量网络(剩余容量)、增广路(从源到汇的可行路径)、分层图(BFS 按距离分层)。当前弧优化是必备的,没有优化的 Dinic 很容易超时。
🎯 直觉理解
想象你在城市的供水系统中,每条水管有一个容量限制。你想知道从水厂到居民区的最大供水量是多少。Dinic 算法先用水位计(BFS)给每个节点标上距离水厂的层数,然后让水从高层流向低层(DFS),直到某一层的水管全部饱和(阻塞流)。然后重新分层,继续输水。
📝 算法流程
- 建图:每条边存储容量和反向边(用于残量网络)
- BFS 分层:从源点出发,给每个节点标上距离层次
- 如果汇点无法到达,算法结束
- DFS 找阻塞流:只沿层次递增的边发送流量
- 当前弧优化:记录每个节点上次遍历到的边,下次从这里继续
- 重复 BFS + DFS 直到无增广路
$$\text{最大流} = \sum \text{从源点发出的流量} = \sum \text{到达汇点的流量}$$
$$\text{最小割} = \text{最大流} \quad \text{(最大流最小割定理)}$$
📊 复杂度分析
| 指标 | 复杂度 |
|---|---|
| 时间 | $O(n^2 m)$(实际远小于上界) |
| 空间 | $O(n + m)$ |
💻 参考实现(C++)
C++ (C++17)
#include
using namespace std;
using ll = long long;
const ll INF = 1e18;
struct Edge {
int to, rev;
ll cap;
};
struct Dinic {
int n, s, t;
vector> g;
vector level, iter;
Dinic(int n) : n(n), g(n), level(n), iter(n) {}
void addEdge(int from, int to, ll cap) {
g[from].push_back({to, (int)g[to].size(), cap});
g[to].push_back({from, (int)g[from].size() - 1, 0});
}
bool bfs() {
fill(level.begin(), level.end(), -1);
queue q;
level[s] = 0; q.push(s);
while (!q.empty()) {
int u = q.front(); q.pop();
for (auto& e : g[u])
if (e.cap > 0 && level[e.to] < 0) {
level[e.to] = level[u] + 1;
q.push(e.to);
}
}
return level[t] >= 0;
}
ll dfs(int u, ll f) {
if (u == t) return f;
for (int& i = iter[u]; i < g[u].size(); i++) {
Edge& e = g[u][i];
if (e.cap > 0 && level[u] < level[e.to]) {
ll d = dfs(e.to, min(f, e.cap));
if (d > 0) {
e.cap -= d;
g[e.to][e.rev].cap += d;
return d;
}
}
}
return 0;
}
ll maxFlow(int s, int t) {
this->s = s; this->t = t;
ll flow = 0;
while (bfs()) {
fill(iter.begin(), iter.end(), 0);
ll f;
while ((f = dfs(s, INF)) > 0) flow += f;
}
return flow;
}
};
int main() {
Dinic dinic(6);
dinic.addEdge(0, 1, 16);
dinic.addEdge(0, 2, 13);
dinic.addEdge(1, 2, 10);
dinic.addEdge(1, 3, 12);
dinic.addEdge(2, 4, 14);
dinic.addEdge(3, 2, 9);
dinic.addEdge(3, 5, 20);
dinic.addEdge(4, 3, 7);
dinic.addEdge(4, 5, 4);
cout << dinic.maxFlow(0, 5) << endl; // 23
return 0;
} ⚠️ 常见坑点
忘记加反向边——反向边初始容量为 0
BFS 分层后没有重置 iter——当前弧优化必须重置
DFS 返回 0 时没有继续搜索——要遍历所有边
INF 设置太小——流量可能很大,INF 要足够大
📚 相关题目
| 题目 | 来源 | 难度 | 备注 |
|---|---|---|---|
| P3376 网络流模板 | 洛谷 | 提高组 | Dinic 模板题 |
| P2756 飞行员配对 | 洛谷 | 提高组 | 二分图匹配 = 网络流 |
| P1343 地震逃生 | 洛谷 | 提高组 | 网络流 + 拆点 |