[luoguP3376] 网络最大流

题意

给出一个网络,求流量的最大值

sol

网络流板子题。
网络流是 OI 中比较常用的算法之一,以较高的建图难度深受出题人喜爱,不过近几年题目数量减少。
当然,在学习建图之前,需要先学会网络流的板子。

一些定义

(部分摘自 OI-Wiki)
网络是特殊的有向图 G=(V,E)
源点 s 是网络的起点,汇点 t 是网络的终点,s,tV
对于(u,v)E,其容量为 c(u,v),特别的,若 (u,v)Ec(u,v)=0
对于(u,v)E,其流量为 f(u,v),且满足以下性质:

  1. (u,v)E,0f(u,v)c(u,v)
  2. uV{s,t},xVf(u,x)=xVf(x,u)

对于一个网络上的一个流,我们定义其流量 |f|=xVf(s,x)xVf(x,s)
对于一个网络 G,若 ST=VST=sS,tT,则 {S,T} 是 G 的一个割,其容量 ||S,T||=uSvTc(u,v)
对于网络 G,其可能的最大的流量 f 即为 G 的最大流

Edmond-Karp

在残量网络中 BFS 找到一条增广路(从源点到汇点容量为正的路径),累加流量,最后更新残量网络。
理论复杂度上限 O(nm2),实际可处理 103104 的数据
代码:

#include <iostream>
#include <algorithm>
#include <cstring>
#include <queue>

using namespace std;
typedef unsigned int UI;
typedef long long LL;

constexpr int N = 205, M = 10005;
constexpr UI INF = 1 << 31;

int h[N], e[M], f[M], ne[M], idx;
int pre[N];
UI d[N];
bool st[N];
int n, m, S, T;

void add(int a, int b, int c){
    e[idx] = b, f[idx] = c, ne[idx] = h[a], h[a] = idx ++ ;
    e[idx] = a, f[idx] = 0, ne[idx] = h[b], h[b] = idx ++ ;
}

bool BFS(){
    memset(st, false, sizeof st);
    queue<int> q;
    st[S] = true, d[S] = INF;
    q.push(S);
    while (!q.empty()) {
        int t = q.front();
        q.pop();
        for (int i = h[t]; ~i; i = ne[i]){
            int j = e[i];
            if (!st[j] && f[i]) {
                st[j] = true;
                pre[j] = i;
                d[j] = min(d[t], (UI) f[i]);
                if (j == T) return true;
                q.push(j);
            }
        }
    }
    return false;
}

LL EK(){
    LL res = 0;
    while (BFS()) {
        res += d[T];
        for (int u = T; u != S; u = e[pre[u] ^ 1]) 
            f[pre[u]] -= d[T], f[pre[u] ^ 1] += d[T];
    }

    return res;
}

int main(){
    scanf("%d%d%d%d", &n, &m, &S, &T);
    memset(h, -1, sizeof h);
    while (m -- ){
        int a, b, c;
        scanf("%d%d%d", &a, &b, &c);
        add(a, b, c);
    }

    printf("%lld\n", EK());

    return 0;
}

Dinic

在残量网络中进行分层(防止出现环),然后爆搜每一条增广路,如果可行的话就累加
注意一部分优化:

  1. 在爆搜的过程中,如果发现当前节点的流出流量已经超过流入流量,则直接返回
  2. 在爆搜的过程中,如果发现某个节点无法提供任何流量,那么就在这个残量网络中删除这个节点
  3. 在爆搜的过程中,如果重复遍历某个点,那么继续上一次未搜索完的弧遍历(当前弧优化)

理论复杂度上限 O(n2m),实际可处理 104105 的数据。
代码

#include <iostream>
#include <algorithm>
#include <cstring>
#include <queue>

using namespace std;
typedef unsigned int UI;

const int N = 1205, M = 240005;
const UI INF = 1 << 31;

int h[N], e[M], ne[M], idx;
UI f[M];
int d[N], cur[N];
int n, m, S, T;

void add(int a, int b, int c){
    e[idx] = b, f[idx] = c, ne[idx] = h[a], h[a] = idx ++ ;
    e[idx] = a, f[idx] = 0, ne[idx] = h[b], h[b] = idx ++ ;
}

bool bfs(){
    memset(d, -1, sizeof d);
    queue<int> q;
    q.push(S), d[S] = 0, cur[S] = h[S];
    while (!q.empty()) {
        int t = q.front(); q.pop();
        for (int i = h[t]; ~i; i = ne[i]){
            int j = e[i];
            if (d[j] == -1 && f[i]) {
                d[j] = d[t] + 1;
                cur[j] = h[j];
                if (j == T) return true;
                q.push(j);
            }
        }
    }
    return false;
}

UI find(int u, UI limit) {
    if (u == T) return limit;
    UI flow = 0;
    for (int i = cur[u]; ~i && flow < limit; i = ne[i]){ // 优化 1
        cur[u] = i; // 优化 3
        int j = e[i];
        if (d[j] == d[u] + 1 && f[i]) {
            int t = find(j, min(limit - flow, f[i]));
            if (!t) d[j] = -1; // 优化 2
            f[i] -= t, f[i ^ 1] += t, flow += t;
        }
    }
    return flow;
}

int dinic(){
    int res = 0, flow;
    while (bfs()) while (flow = find(S, INF)) res += flow;
    return res;
}

int main(){
    scanf("%d%d%d%d", &n, &m, &S, &T);
    memset(h, -1, sizeof h);
    while (m -- ){
        int a, b, c;
        scanf("%d%d%d", &a, &b, &c);
        add(a, b, c);
    }

    printf("%d\n", dinic());
}

Dinic 算法在大部分题目中都够用,因为卡 Dinic 是没有浮木的行为。
此外还有效率差不多的 ISAP 算法和相对更快的 HLPP 算法(理论上届复杂度 O(n2m)),但是不会。

posted @   是一只小蒟蒻呀  阅读(7)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示