zoukankan      html  css  js  c++  java
  • P3376 网络流-最大流模板题(Dinic+当前弧优化)

    (点击此处查看原题)

    Dinic算法

    Dinic算法相对于EK算法,主要区别在于Dinic算法对图实现了分层,使得我们可以用一次bfs,一次dfs使得多条增广路得到增广

    普通的Dinic算法已经可以处理绝大多数最大流(最小割)的题目了,但是总是有些题目会卡住普通的Dinic算法,此时我们就需要用到当前弧优化了

    当前弧优化简述

    不要小看当前弧优化,这个优化效果可是很明显的,就这个例题来说,我用普通的Dinic算法用时约1.7s,而使用了当前弧优化的Dinic算法后,只用了176ms,由此可以看出这个优化的强大

    当前弧优化的核心思想为:避免遍历已经满流的边,总所周知,已经满流的边已经再构成增广路,而普通Dinic算法中,我们总是遍历所有的边,后判断这条边是否满流,这样相当于白白消耗时间

    回想一下我们普通Dinic算法中将增广路增广的方法:我们总是尽可能地将某一条边的容量完全利用,因为我们建立了反边,可以“反悔”,因此我们可以完全利用这条边的容量;而对于被完全利用的边,因为这条边已经满流了,之后的增广路中不会用到这条边,那么我们就可以标记一下从每个点出发的第一条不满流边,下次从这个点开始求增广路的时候,就可以跳过之前已经满流的那些边,直接从不满流边开始遍历了

    具体操作的话,我们用cur数组记录以每个点为起点的边当前可用的第一条不满流边,在dfs之前我们将head数组的值复制给cur数组,然后再dfs将增广路增广的时候,我们记录下以i为起点的第一条不满流边,即cur[i],因为我们从i继续向后增广的时候,只要流入流量flow_in用尽,当前枚举到的这条边之前的边都是满流的了,所以我们记录下最后一条遍历的边作为cur[i],下次遍历从这个点出发的边的时候,从cur[i]开始遍历即可。

    代码区

    #include<iostream>
    #include<cstdio>
    #include<algorithm>
    #include<cstring>
    #include<queue>
    #include<string>
    #include<fstream>
    #include<vector>
    #include<stack>
    #include <map>
    #include <iomanip>
    
    #define bug cout << "**********" << endl
    #define show(x, y) cout<<"["<<x<<","<<y<<"] "
    #define LOCAL = 1;
    using namespace std;
    typedef long long ll;
    const int inf = 0x3f3f3f3f;
    const ll mod = 1e6 + 3;
    const int Max = 1e5 + 10;
    
    struct Edge {
        int to, next, flow;    //flow记录这条边当前的边残量
    }edge[Max << 1];
    
    
    int n, m, s, t;
    int head[Max], tot;
    int dis[Max],cur[Max];
    
    void init()
    {
        memset(head, -1, sizeof(head));tot = 0;
    }
    
    void add(int u, int v, int flow)
    {
        edge[tot].to = v;
        edge[tot].flow = flow;
        edge[tot].next = head[u];
        head[u] = tot++;
    }
    
    bool bfs() //判断图是否连通
    {
        queue<int>q;
        memset(dis, -1, sizeof(dis));
        dis[s] = 0;
        q.push(s);
        while (!q.empty())
        {
            int u = q.front();q.pop();
            for (int i = head[u]; i != -1; i = edge[i].next)
            {
                int v = edge[i].to;
                if (dis[v] == -1 && edge[i].flow > 0)        //可以借助边i到达新的结点
                {
                    dis[v] = dis[u] + 1;                    //求顶点到源点的距离编号
                    q.push(v);
                }
            }
        }
        return dis[t] != -1;                                //确认是否连通
    }
    
    int dfs(int u, int flow_in)
    {
        if (u == t) return flow_in;
        int flow_out = 0;                                    //记录这一点实际流出的流量
        for (int i = cur[u]; i != -1;i = edge[i].next)
        {
            cur[u] = i;                                     //由于我们增广的时候都是尽量用尽这条边的容量为前提的,
                                                            // 那么在在用尽流入流量之前,我们使用的边,都已经满流了
                                                            //没有继续增广的可能了,所以抛弃了这些重复的边,从未满流的边开始遍历
            int v = edge[i].to;
            if (dis[v] == dis[u] + 1 && edge[i].flow > 0)
            {
                int flow_part = dfs(v, min(flow_in, edge[i].flow));
                if (flow_part == 0)continue;                //无法形成增广路
                flow_in -= flow_part;                        //流出了一部分,剩余可分配流入就减少了
                flow_out += flow_part;                        //记录这一点最大的流出
    
                edge[i].flow -= flow_part;
                edge[i ^ 1].flow += flow_part;                //减少增广路上边的容量,增加其反向边的容量
                if (flow_in == 0)
                    break;
            }
        }
        return flow_out;
    }
    
    int max_flow()
    {
        int sum = 0;
        while (bfs())
        {
            for(int i = 1;i <= n ;i ++)
                cur[i] = head[i];
            sum += dfs(s, inf);
        }
        return sum;
    }
    
    
    int main() {
    #ifdef LOCAL
        //freopen("input.txt", "r", stdin);
        //freopen("output.txt", "w", stdout);
    #endif
        while (scanf("%d%d%d%d", &n, &m, &s, &t) != EOF)
        {
            init();
            for (int i = 1, u, v, flow;i <= m; i++)
            {
                scanf("%d%d%d", &u, &v, &flow);
                add(u, v, flow);add(v, u, 0);
            }
    
            printf("%d
    ", max_flow());
        }
    
        return 0;
    }
    View Code
  • 相关阅读:
    【2020-02-02】禅修无处不在
    【2020-02-01】接受改变这个常态
    2 分法查找内容
    python 单例模式
    day 34 js 基础后部分 BOM 和 事件和正则
    第三次网编考试
    day 33js 后续 函数.对象
    爬虫 自动生成请求头教程
    请求数据分析 xpath语法 与lxml库
    sanic 计划学习这个
  • 原文地址:https://www.cnblogs.com/winter-bamboo/p/11385276.html
Copyright © 2011-2022 走看看