【板子】Dinic(最大网络流)

通用 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] 核心!改原图残量

注意事项

  1. N 开够

    • 点编号从 1 开始也要保证 N > max_vertex
  2. M 要 ×2

    • 因为每条边要建一条反向边
  3. 多测记得清空

    复制代码
    for (int i = 1; i <= n; i++) g[i].clear();
  4. 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()});
  • uv,容量是 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;
}

这里发生了什么?

  1. BFS 建层

  2. DFS 多路增广(阻塞流)

  3. 重复直到无法到达 t

📌 每一轮 BFS 后,dfs会被反复调用,直到当前分层图被"堵死"。


五、一张"对照表":代码 vs 现实

代码 现实含义
Edge::cap 水管还能流多少
Edge::rev 反悔用的反向水管
depth[] 城市在第几站
bfs 画地图、编号
dfs 送水工出发
cur[] 送水工的小本子
while(dfs) 把这一层水送干
相关推荐
我星期八休息17 小时前
网络编程—应用层HTTP协议
linux·运维·开发语言·前端·网络·网络协议·http
ITxiaobing202318 小时前
IP库与AppsFlyer数据对账指南:破解国家/地区数据不一致的难题
网络·网络协议
有浔则灵18 小时前
GORM钩子函数详解
网络·安全·web安全
汇智信科20 小时前
图谱、向量、关键词、SQL多路一体,FastKG垂域召回率100%拉满
网络·数据库·ai编程·汇智信科·hsim·fastclaw·汇智龙虾
liulilittle20 小时前
论分布式系统的半开闭问题
服务器·网络·分布式·系统架构·竞态
FreeBuf_20 小时前
仅用六分钟,黑客借助Gemini CLI自主构建并迁移C&C僵尸网络
网络·人工智能
cyforkk20 小时前
Vercel 绑定自定义域名极简配置指南
服务器·前端·网络
Yang96111 天前
探索无线新视界:鼎讯信通DXMP系列频谱仪模块深度剖析
运维·网络
进阶的猪1 天前
【无标题】vmware虚拟机无法勾选**将主机虚拟适配器连接到此网络** /没有网络(不显示网络图标)的解决方法
网络