如何用 vector 实现最大流(题解版)

· · 题解

说明

本文是截至目前为止中文互联网对 vector 实现最大流算法介绍最详细的博客,如果您是初学网络流的 vector 党因为网上对 vector 实现网络流的介绍太少而为难,不妨来看看我的方法。

由于网上已经有很多教程了,所以我对最大流算法的原理不会有特别清楚的介绍,本文着重介绍网上没有教程的如何用 vector 实现最大流。

vector 的局限性

网络流题目中,vector 的局限性主要体现在于:难以实现快速找到反向边。

本文将详细讲解如何消除 vector 的这种局限性。

解决方案

那么 vector 怎么找反向边呢?

我们知道,一条反向边的起点是 st,终点是 ed 的话,按照常规储存方法,我们是这样存图的:

struct edge
{
    int ed, len;
}
vector <edge> e[N];

此时我们发现,如果想要找到反向边的话,必须要遍历一遍 e[ed] 数组,这样做的最坏复杂度是 O(m) 的。

这样的话我们就会在原有的时间复杂度上乘上一个 m,让 EK 算法的复杂度变成 O(nm^3),Dinic 的复杂度变成 O(n^2m^2),本来人家效率就低,你这么一搞简直变本加厉。

那么我们如何快速找到反向边呢?

我们不难想到,如果我们记录下反向边的存储位置为 id,那么我们寻找反向边的时候只需要访问 e[ed][id] 就可以了,大大节约了我们的时间。

那么这个 id 的值应该是多少呢?

显然,当我们新加入一条边之前,e[ed] 数组里存的数的个数为 e[ed].size(),它们的下标从 0e[ed].size() - 1,那么新存进来的数的下标一定就是 e[ed].size(),所以 id 直接设为 e[ed].size() 即可。

代码实现:

struct edge {int ed, len, id;};
vector <edge> e[N];

加边:

    while (m -- )
    {
        int st, ed, len;
        scanf("%lld%lld%lld", &st, &ed, &len);
        int sti = e[st].size();
        int edi = e[ed].size();
        e[st].push_back((edge){ed, len, edi});
        e[ed].push_back((edge){st, 0, sti});
    }

关于 EK 算法的 pre 数组

我们知道在链前实现 EK 算法的时候我们通常会开一个 pre 数组记录当前这个点是走哪条边跳过来的,在 vector 实现时,显然你光记录走哪条边跳过来的用处不大,关键还要知道从哪个点跳过来的。

所以不妨再开一个 lst 数组记录你是从哪个点跳过来的,代码实现如下:

int lst[N], pre[N], d[N];
bool vis[N];

bool bfs()
{
    memset(vis, false, sizeof vis);
    queue <int> q;
    q.push(S);
    vis[S] = true;
    d[S] = INF;
    while (!q.empty())
    {
        int t = q.front();
        q.pop();
        for (int i = 0; i < e[t].size(); i = i + 1)
        {
            int cur = e[t][i].ed;
            if (!vis[cur] && e[t][i].len)
            {
                vis[cur] = true;
                d[cur] = min(d[t], e[t][i].len);
                lst[cur] = t;
                // lst 记录当前这个点是从第几个点跳过来的
                pre[cur] = i;
                // pre 记录当前这个点是从第几条边跳过来的
                if (cur == T)
                    return true;
                q.push(cur);
            }
        }
    }
    return false;
}

然后是 EK 算法修改正向边和反向边边权的部分:

int EK()
{
    int r = 0;
    while (bfs())
    {
        r += d[T];
        for (int i = T; i != S; i = lst[i])
        {
            e[lst[i]][pre[i]].len -= d[T];
            // 从 lst[i] 沿着 pre[i] 跳到 i 的边是正向边
            e[i][e[lst[i]][pre[i]].id].len += d[T];
            // 从 i 沿着 e[lst[i]][pre[i]].id 跳回 lst[i] 的边是反向边
        }
    }
    return r;
}

这个大致就是 EK 算法如何用 vector 实现。

完整代码可以参考:点我查看

vector 实现 Dinic 算法

我们都知道,Dinic 算法和 EK 的最大的不同点在于每次可以增广多条路,而同时增广多条路就需要一个 dfs,这样的话其实我们只要实现了 dfs 的部分,其他就可以用和 EK 一样的方式实现了。

dfs 的部分和链前的部分几乎是一样的,唯一不同的是当前弧优化怎么实现。

这是链式前向星的当前弧优化:

    for (int i = cur[st]; ~i; i = nxt[i])
    {
        cur[st] = i;  // 当前弧优化
        ……
    }

我们发现,其实就是需要记录当前没有流满的是到哪个点的路径,那么同理到 vector,我们发现也只要记录当前到没有流满的点的第一条路径即可:

    for (int i = cur[st]; i < e[st].size(); i = i + 1)
    {
        cur[st] = i;  // 当前弧优化
        ……
    }

其实大抵都是一样的,只要解决了找反边的问题,vector 是很容易实现网络流的。

因为 Dinic 算法比较常用,我把 vector 实现 Dinic 算法的完整代码直接贴在下面:

#include <bits/stdc++.h>
#define int long long
using namespace std;

const int N = 10010, M = 200010, INF = 1e15;

struct edge
{
    int ed;
    int len;
    int id;
};

vector <edge> e[N];

int n, m, S, T;
int dep[N], cur[N];

bool bfs()
{
    memset(dep, -1, sizeof dep);
    queue <int> q;
    q.push(S);
    dep[S] = 0;
    while (!q.empty())
    {
        int t = q.front();
        q.pop();
        for (int i = 0; i < e[t].size(); i = i + 1)
        {
            int ed = e[t][i].ed;
            if (dep[ed] == -1 && e[t][i].len)
            {
                dep[ed] = dep[t] + 1;
                q.push(ed);
            }
        }
    }
    memset(cur, 0, sizeof(cur));
    return (dep[T] != -1);
}

int dfs(int st, int limit)
{
    if (st == T)
        return limit;
    for (int i = cur[st]; i < e[st].size(); i = i + 1)
    {
        cur[st] = i;  // 当前弧优化
        int ed = e[st][i].ed;
        if (dep[ed] == dep[st] + 1 && e[st][i].len)
        {
            int t = dfs(ed, min(e[st][i].len, limit));
            if (t)
            {
                e[st][i].len -= t;
                e[ed][e[st][i].id].len += t;
                return t;
            }
            else
                dep[ed] = -1;
        }
    }
    return 0;
}

int dinic()
{
    int r = 0, flow;
    while (bfs()) while (flow = dfs(S, INF)) r += flow;
    return r;
}

signed main()
{
    cin >> n >> m >> S >> T;
    while (m -- )
    {
        int st, ed, len;
        cin >> st >> ed >> len;
        int sti = e[st].size();
        int edi = e[ed].size();
        e[st].push_back((edge){ed, len, edi});
        e[ed].push_back((edge){st, 0, sti});
    }
    cout << dinic();

    return 0;
}

完美,撒花

✿✿ヽ(°▽°)ノ✿