【模板】网络最大流

OJ: luogu

题目 ID: P3376

标签: 网络流

日期: 2025-12-22 10:12

使用EK算法

include-code failed: /code/graph/luogu-p3376-ek.cpp

Dinic 算法 朴素

cpp
/* 
 * 多路增广 Dinic
 * */
#include <cstdio>
#include <cstring>
#include <cmath>
#include <vector>
#include <deque>
#include <list>
#include <queue>
#include <stack>
#include <map>
#include <set>
#include <algorithm>
using namespace std;

#define maxn 10005
#define maxe 200005

int n,m,s,t;
int dep[maxn]; //分层,点i的层数


//向量星
struct _e {
    int u,v,next,cap;
};
_e e[maxe];
int head[maxn];
int cnt = 0;

//边的编号从0开始
void addEdge(int u,int v,int cap){
    e[cnt].u = u;
    e[cnt].v = v;
    e[cnt].cap = cap;
    e[cnt].next = head[u];
    head[u] = cnt++;
}


//返回值表示是否能达到t点
bool bfs(){ //给各个点分层
    memset(dep,-1,sizeof(dep)); //初始化
    queue<int> q;
    dep[s] = 1; //起点的 层
    q.push(s);
    
    while(q.empty() == false){
        int now = q.front(); q.pop();
        int i;
        for(i=head[now];i!=-1;i=e[i].next){
            int v = e[i].v;
            //v没有访问过 且 容量可通行
            if(dep[v] == -1 && e[i].cap >0){ 
                dep[v] = dep[now] +1;
                q.push(v);
            }
        }
    }

    return dep[t] != -1; // !=-1 表示能分层到汇点
}

// u 当前点,low u前面的路径上的最小容量
int dfs(int u,int low){
    if( u == t) return low;
    int i;
    int ret = low;
    for(i=head[u]; i !=-1;i=e[i].next){
        int v = e[i].v;
        // 是下一层的点 且 可通行
        if(dep[v] == dep[u]+1 && e[i].cap > 0){
            int flow = dfs(v,min(ret,e[i].cap));

            //剪枝 去掉增广完毕的点
            // 证明: flow ==0 点v
            if( flow == 0) dep[v] = 0;

            e[i].cap -=flow;    //同向容量 缩小
            e[i^1].cap += flow; //反向容量 加大
            ret -= flow;

            if(!ret) break;
        }
    }
    
    return low-ret;
}

int dinic(){
    int tmp  = 0;
    while( bfs()){ //分层
        tmp += dfs(s,0x7f7f7f7f);
    }

    return tmp;
}


int main(){
    
    scanf("%d%d",&n,&m);
    scanf("%d%d",&s,&t);
    int t1,t2,t3;
    int i,j,k;
    memset(head,-1,sizeof(head));
    for (i=1;i<=m;i++){
        scanf("%d%d%d",&t1,&t2,&t3);
        addEdge(t1,t2,t3); //正向边
        addEdge(t2,t1,0);  //反向边
    }
    int maxflow =dinic();
    printf("%d\n",maxflow);
    return 0;
}

Dinic 算法 优化

  • 多路增广
  • 当前弧优化
  • 炸边优化
cpp
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e6+5; // 点
const int maxe = 2e6+5; // 边 (注意:要是题目边数的2倍)
const long long INF = 1e18;

// 存图的模板
struct linkList {
    typedef struct {int u,v,w,next;} edge;
    edge e[maxe];
    int h[maxn],edge_cnt=0;
    linkList(){
        edge_cnt=0;
        memset(h,-1,sizeof(h));
    }

    //遍历点u 周围点
    template<typename U>
    void for_each(int u,U func){
        for(int i = h[u] ; i !=-1;i = e[i].next)
            func(e[i].u,e[i].v,e[i].w); //u v w
    }

    void add(int u,int v,int w=0){
        e[edge_cnt] = {u,v,w,h[u]};
        h[u] = edge_cnt++;
    }
    void add2(int u,int v,int w=0){
        add(u,v,w);
        add(v,u,w);
    }
    //下标访问
    edge& operator[](int i){ return e[i]; }
    //返回head[u]
    int operator()(int u){ return h[u]; }
} e;


// Dinic算法最大流模板 - 基于linkList存图
struct Dinic {
    vector<int> level, iter;  // level: BFS分层, iter: 当前弧优化
    int n;  // 节点数
    
    // 初始化,n为节点数(节点编号从0开始)
    Dinic(int n) : n(n), level(n+5), iter(n+5) {
        // 重置linkList
        e.edge_cnt = 0;
        memset(e.h, -1, sizeof(e.h));
    }
    
    // 添加边:从u到v,容量为cap
    // 使用技巧:正向边和反向边的索引相差1,通过异或1来找到对应边
    void addEdge(int u, int v, long long cap) {
        e.add(u, v, cap);    // 正向边,w字段存储容量
        e.add(v, u, 0);      // 反向边,容量为0
    }
    
    // BFS分层,构建层次图
    bool bfs(int s, int t) {
        fill(level.begin(), level.end(), -1);
        queue<int> q;
        level[s] = 0;
        q.push(s);
        
        while (!q.empty()) {
            int u = q.front();
            q.pop();
            
            // 使用linkList的遍历方式
            e.for_each(u, [&](int from, int to, int cap) {
                if (cap > 0 && level[to] < 0) {
                    level[to] = level[u] + 1;
                    q.push(to);
                }
            });
        }
        
        return level[t] >= 0;  // 返回是否能到达汇点
    }
    
    // DFS寻找增广路
    // 到达点u流量为preFlow
    // 计算从点u出发的最大流,到达点t
    // 本质是一个DAG 上的dp
    long long dfs(int u, int t, long long preFlow) {
        if (u == t || preFlow == 0) return preFlow;
        long long flow = 0;
        
        // 当前弧优化:从iter[u]开始遍历
        for (int& cid = iter[u]; cid != -1; cid = e[cid].next) {
            auto& edge = e[cid]; // 当前弧优化
            int to = edge.v;
            long long cap = edge.w;
            
            if (level[u] + 1 != level[to] || cap <= 0) continue;
            
            long long tr = dfs(to, t, min(preFlow, cap));

            // 更新容量
            e[cid].w -= tr ;     // 正向边容量减少
            e[cid ^ 1].w += tr; // 反向边容量增加(利用异或找到反向边)
            flow += tr;
            preFlow -= tr;
            if (preFlow == 0) break;
        }
        
        // 炸点优化
        // 剪枝:取掉增广完毕的点
        if (flow == 0) level[u] = -1;
        return flow;
    }
    
    // 求从s到t的最大流
    long long maxFlow(int s, int t) {
        long long flow = 0;
        while (bfs(s, t)) {  // 能够分层
            // 当前弧优化重置:将iter设置为每个节点的第一条边
            for (int i = 0; i <= n; i++) {
                iter[i] = e(i);  // 使用linkList的operator()获取head[i]
            }
            
            // 多路增广
            flow += dfs(s, t, LLONG_MAX);
        }
        return flow;
    }
};

// 使用示例:
int main() {
    ios::sync_with_stdio(0); cin.tie(0);
    
    int n, m;  // n个节点,m条边
    int s,t; // 源点 汇点
    std::cin >> n >> m;
    std::cin >> s >> t;
    
    Dinic dinic(n);  // 创建Dinic实例
    
    for (int i = 0; i < m; i++) {
        int u, v;
        long long cap;
        cin >> u >> v >> cap;
        dinic.addEdge(u, v, cap);  // 添加有向边
        // 如果是无向边,需要再加一条反向边:dinic.addEdge(v, u, cap);
    }

    cout << dinic.maxFlow(s,t) << "\n";
    
    return 0;
}

/*
复杂度分析:
- 时间复杂度:O(V²E) 一般情况下表现很好,对于单位容量网络是O(min(V^(2/3), E^(1/2)) * E)
- 空间复杂度:O(V + E)

使用说明:
1. 创建Dinic实例:Dinic dinic(n);
2. 添加边:dinic.addEdge(u, v, cap);
3. 求最大流:long long flow = dinic.maxFlow(source, sink);

注意事项:
- 节点编号从0开始
- 如果题目给的是1-indexed,记得转换
- 容量使用long long防止溢出
- 无向边需要添加两条有向边
*/