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\n", max_flow());
    }

    return 0;
}
View Code
posted @ 2019-08-20 20:08  winter-bamboo  阅读(428)  评论(0编辑  收藏  举报