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
  • 相关阅读:
    ABAP接口用法
    监听textarea数值变化
    The first step in solving any problem is recognizing there is one.
    Wrinkles should merely indicate where smiles have been.
    God made relatives.Thank God we can choose our friends.
    Home is where your heart is
    ABAP跳转屏幕
    Python 工具包 werkzeug 初探
    atom通过remote ftp同步本地文件到远程主机的方法
    Mongodb学习笔记一
  • 原文地址:https://www.cnblogs.com/winter-bamboo/p/11385276.html
Copyright © 2011-2022 走看看