图论专题 - 解题报告 - H

这篇博客详细介绍了网络流问题中的增广路概念,以及如何利用Dinic算法求解最大流。通过增广路定理,当网络中不存在增广路时达到最大流。博主分享了引入反向边的技巧,以允许在寻找最优解过程中进行调整。文章还提及了使用分层图的Dinic算法,通过 BFS 分配节点深度,并用 DFS 寻找增广路,确保路径深度递增。最后,提供了基于题意建图并应用 Dinic 算法求解的具体AC代码。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

首先整个题就透露着那么几个字:网络流板子。但是不会咋整啊,学呗。
这里整理了一下网络流的求法知识啥的。
这里引用一下别人博客的内容,这一篇是找到的比较好的了

增广路

  1. 找到一条从源点到汇点的路径,使得路径上任意一条边的残量>0(注意是小于而不是小于等于,这意味着这条边还可以分配流量),这条路径便称为增广路
  2. 找到这条路径上最小的F[u][v](我们设F[u][v]表示u->v这条边上的残量即剩余流量),下面记为flow
  3. 将这条路径上的每一条有向边u->v的残量减去flow,同时对于起反向边v->u的残量加上flow(为什么呢?我们下面再讲)
  4. 重复上述过程,直到找不出增广路,此时我们就找到了最大流

这个算法是基于增广路定理(Augmenting Path Theorem):网络达到最大流当且仅当残留网络中没有增广路
图例:
在这里插入图片描述


至于如何实现这样的增广路流通?前人的巧妙做法是引入一条反向边

我们知道,当我们在寻找增广路的时候,在前面找出的不一定是最优解,如果我们在减去残量网络中正向边的同时将相对应的反向边加上对应的值,我们就相当于可以反悔从这条边流过。
比如说我们现在选择从u流向v一些流量,但是我们后面发现,如果有另外的流量从p流向v,而原来u流过来的流量可以从u->q流走,这样就可以增加总流量,其效果就相当于p->v->u->q,

在这里插入图片描述


朴素的反向边求最大流的算法通常很慢,通常都是选择dinic算法,因为引入了分层图的思想所以有明显的优化。
具体就是对于每一个点,我们根据从源点开始的bfs序列,为每一个点分配一个深度,然后我们进行若干遍dfs寻找增广路,每一次由u推出v必须保证v的深度必须是u的深度+1。
核心bfs+dfs代码如下:

bool bfs(int start, int goal){
    memset (depth, -1, sizeof(depth));
    queue <int> q;
    depth[start] = 0;
    q.push(start);
    while (!q.empty())
    {
        int now = q.front(); q.pop();
        for (int i = head[now]; ~i; i = edge[i].nex)
        {
            int to = edge[i].to;
            if (depth[to] < 0 && edge[i].dis > 0)					//目标深度没有更新过,且当前是正序的路
            {
                depth[to] = depth[now] + 1;
                q.push(to);
            }
        }
    }
    return depth[goal] > 0;					//只要能到终点,就判定可以有返回的反向流量
}	

int dfs(int now, int goal, int limit) {					//从当前点开始返回,limit表示当前流量的限制,最初始应当为inf,但是每层递归都会影响
        if(now == goal) return limit;					//如果现在已经达到了终点gaol,那么流量全部都可流
        for(int & i = cur[now]; ~i; i = edge[i].nex) {				//cur[]是head[]的copy,这是弧优化的操作,在当前点流出去的路设为下一次不会用到,因为要反复遍历没必要,弧优化跳过
            int to = edge[i].to;
            if(edge[i].dis > 0 && depth[to] > depth[now]) {					//目标深度必定大于当前深度,这就是分层图的选择
                int d = dfs(to, goal, min(limit, edge[i].dis));				//返回流量看看流了多少了
                if(d > 0) {
                    edge[i].dis -= d;						//正向流通的流量要减少
                    edge[i ^ 1].dis += d;					//反向边有返回的机会应当增加
                    return d;								//向上层递归程序返回值同样是d
                }
            }
        }
        depth[now] = -1; 							//把当前点设定为不再外流
        return 0;									//运行到这里说明没有能流到终点的流量
}

int dinic(int start, int goal){
    int flow = 0, f;
    while (bfs(start, goal))
    {
        for (int i = start; i <= goal; i++)
            cur[i] = head[i];
        while (f = dfs(start, goal, inf))
            flow += f;							//dinic的流量累计
    }
    return flow;
}

在很多的网络流题目中,关键就是找到建图的思路,然后dinic一般不会被卡,本题太裸,所以照着题意建立有向边流量,起点是1终点是n,dinic最大流完事。
AC代码:

#include<bits/stdc++.h>
#define FOR(a, b, c) for(int a=b; a<=c; a++)
#define maxn 500005
#define maxm 55
#define hrdg 1000000007
#define zh 16711680
#define inf 2147483647
#define llinf 9223372036854775807
#define ll long long
#define pi acos(-1.0)
#define ls p<<1
#define rs p<<1|1
#define int long long
using namespace std;

inline int read(){
    char c=getchar();long long x=0,f=1;
    while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
    while(c>='0'&&c<='9'){x=x*10+c-'0';c=getchar();}
    return x*f;
}

int n, m, u, v, d, max_flow, depth[maxn];
struct Edge{ int nex, to, dis;}edge[maxn<<1];
int head[maxn<<1], tot, cur[maxn<<1];
void add_egde(int u, int  v, int d){
    edge[tot] = {head[u], v, d}; head[u] = tot++;
    edge[tot] = {head[v], u, 0}; head[v] = tot++;
}

bool bfs(int start, int goal){
    memset (depth, -1, sizeof(depth));
    queue <int> q;
    depth[start] = 0;
    q.push(start);
    while (!q.empty())
    {
        int now = q.front(); q.pop();
        for (int i = head[now]; ~i; i = edge[i].nex)
        {
            int to = edge[i].to;
            if (depth[to] < 0 && edge[i].dis > 0)
            {
                depth[to] = depth[now] + 1;
                q.push(to);
            }
        }
    }
    return depth[goal] > 0;
}

int dfs(int now, int goal, int limit) {
        if(now == goal) return limit;
        for(int & i = cur[now]; ~i; i = edge[i].nex) {
            int to = edge[i].to;
            if(edge[i].dis > 0 && depth[to] > depth[now]) {
                int d = dfs(to, goal, min(limit, edge[i].dis));
                if(d > 0) {
                    edge[i].dis -= d;
                    edge[i ^ 1].dis += d;
                    return d;
                }
            }
        }
        depth[now] = -1; 
        return 0;
}

void init(){
    memset(head, -1, sizeof(head));
    tot = 0;
}

int dinic(int start, int goal){
    int flow = 0, f;
    while (bfs(start, goal))
    {
        for (int i = start; i <= goal; i++)
            cur[i] = head[i];
        while (f = dfs(start, goal, inf))
            flow += f;
    }
    return flow;
}

signed main()
{
    init();
    n = read(); m = read();
    for (int i = 1; i <= m; i++)
    {
        u=read(); v=read(); d=read();
        add_egde(u, v, d);
        add_egde(v, u, 0);
    }
    printf("%lld\n", dinic(1, n));
    return 0;					//发现好像没啥好注释的了
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值