网络最大流[dinic]

模板题:P3376 【模板】网络最大流
视频讲解

核心:

\(bfs\) 寻找增广路并对图进行分层,要严格按照深度递增,这样可以降低 \(dfs\)搜索深度,\(dfs\) 求增广路最大流量。

优化:

概要:

  1. 当前弧优化,当前增广路寻找最大流,之前增广路搜过的边都被榨干了,可以不要了(小优化);
  2. 多路增广:到达每一个节点,都尽可能的增广,直到增广到汇点;
  3. 炸点:若点在 \(dfs\) 中无法返回值,则意味着它被榨干了,就炸掉它;

详解:

  1. 当前弧优化:for. e.g. 中间节点 \(A\),有三个流向,\(A\)->\(B\)\(A\)->\(C\)\(A\)->\(D\),在 \(A\) 点时,如果 \(A\) -> \(B\)\(A\)->\(C\) 这两个边都已经满流,那么下次再经过 \(A\) 节点时,直接跳过 \(A\)->\(B\)\(A\)->\(C\) 这两条边,直接访问 \(A\)->\(C\) 这个点,这个就是当前弧优化,实现方式,可开一个数组记录每个节点访问到第几条边。
  2. 多路增广:到达每一个节点,都尽可能的增广,直到增广到汇点。
  3. 炸点:一次分层,多次寻找增广路线,在一次寻找增广路线中,发现中间节点 \(A\),有三个流向,\(A\)->\(B\)\(A\)->\(C\)\(A\)->\(D\),这个三个流向都满流了,那么这个节点就不能作为中间节点出现了,因为它的流向都已经作为增广路线,全部占满了。具体操作为 \(depth_A = 0\)

代码

#include <iostream>
#include <cstdio>
#include <cstring>
#include <queue>
#define MAXN 205
#define MAXM 5005
using namespace std;
typedef long long ll;
typedef unsigned long long ull;

const ll inf = 1 << 30;
int n, m, s, t;
struct edge
{
    int v, nxt;
    long long w;
}G[MAXM << 1];
int head[MAXN], cntEdge = 1, cur[MAXN], depth[MAXN];
ull maxFlow = 0;

ull read()
{
    ull x = 0; char ch = getchar();
    while (ch < '0' || ch > '9')
        ch = getchar();
    while (ch >= '0' && ch <= '9')
    {
        x = (x << 3) + (x << 1) + (ch ^ 48);
        ch = getchar();
    }
    return x;
}

inline void addEdge(int u, int v, int w)
{
    ++cntEdge;
    G[cntEdge].v = v;
    G[cntEdge].w = w;
    G[cntEdge].nxt = head[u];
    head[u] = cntEdge;
}

void init()
{
    n = read(), m = read(), s = read(), t = read();
    for (int i = 1; i <= m; ++i)
    {
        int u = read(), v = read(), w = read();
        addEdge(u, v, w);
        addEdge(v, u, 0);   // 建反边
    }        
}

bool bfs()  // 判断是否存在增广路并构造增广路
{
    memset(depth, 0, sizeof(depth));
    queue <int> q;
    q.push(s); depth[s] = 1;
    cur[s] = head[s];
    while (!q.empty())
    {
        int u = q.front(); q.pop();
        for (int i = head[u]; i; i = G[i].nxt)
        {
            if (G[i].w && !depth[G[i].v])
            {
                depth[G[i].v] = depth[u] + 1;
                cur[G[i].v] = head[G[i].v];
                q.push(G[i].v);
                if (G[i].v == t)    // 只用找到一条增广路就行了
                    return 1;
            }
        }
    }
    return depth[t];
}

ll dfs(int u, ll flow)
{
    if (u == t) return flow;    // 搜索边界:到达汇点
    ll ret = 0;
    for (int i = cur[u]; i && flow; i = G[i].nxt) // flow 用完了要及时退出循环
    {
        cur[u] = i; // 当前弧优化灵魂,之前增广路搜过的边都被榨干了,可以不要了
        if (G[i].w && depth[G[i].v] == depth[u] + 1)    // 由于有反边,所以要向深度更大的点搜索
        {
            ll x = dfs(G[i].v, min(flow, G[i].w));
            G[i].w -= x, G[i ^ 1].w += x;   // 反边的灵魂:用了多少,就给反边加上多少
            flow -= x, ret += x;
        }
    }
    if (!ret) depth[u] = 0; // 该点被榨干了,炸掉它
    return ret;
}

void solve()
{
    while (bfs())
        maxFlow += dfs(s, inf); // 源点无线流量
    printf("%llu", maxFlow);
}

int main()
{
    init();
    solve();
    return 0;
}
posted @ 2022-10-19 20:39  晨曦墨凝  阅读(18)  评论(0)    收藏  举报