网络流

Posted akakw1

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了网络流相关的知识,希望对你有一定的参考价值。

topic

持续更新

剩余内容:

  • ISAP
  • HLPP
  • 费用流
  • 上下界网络流

网络流

基本概念

定义

给定一个有向图,其中有一个源点(起点)和一个汇点(终点),每条边有一个容量 (c_i) 和一个流量 (f_i) 表示最大可以通过这条边的流量和这条边的流量,如果满足 (f_i leq c_i) 且每个点出边的流量和等于这个点入边的流量和(其中源点出边的流量等于汇点入边的流量),就称这是一个网络流

简单理解:有一些节点和一些单向水管,水管连接两个节点且每个单位时间内只能通过一定的水,我们从一个节点持续灌水,从一个节点接水,达到恒定状态后的水流就是一个网络流

形象描述:在不超过流量限制的情况下,“流”从源点不断产生,流经网络图,最终全部归于汇点

最大流

顾名思义,一个网络流使得从源点到汇点的总流量最大

最小割

删去一些边使得源点和汇点不连通且删去边的容量和最小

增广路

若一条从源点到汇点的路径上各边的剩余流量都大于 0,则称这条路径是一条增广路

残量网络

网络中所有节点及剩余流量大于 (0) 的边构成的子图被称为残量网络

最大流

Edmonds-Karp 增广路算法

每次从源点开始 (BFS) 找到增广路,然后进行让一个流从源点经过增广路到达汇点,并更新剩余流量,直到无法找到增广路为止

具体方法

每次 (BFS) 时只考虑剩余流量大于 (0) 的边,并记录下剩余流量的最小值,然后再把这个最小值加入答案,并把增广路上的每条边的剩余流量减去这个最小值

但是我们发现,这样找出来的是一个可行流而不一定是最大流,原因是我们可能使用了一个不优的流,所以我们还要构建这个图的反图(即一个图,图中的每条边的剩余流量为原图中已经使用的流量),这样我们就可以支持撤回操作了,即每次找增广路的时候同时考虑反图(直接把反图当做原图处理),如果一个流经过了反图中的边,则这个流中有撤回操作

实现方法

关于反图的构建,我们可以采用邻接表成对存储的方法,即对于边 (i),编号为 (i xor 1) 的边为残量网络,然后 (BFS) 就和正常的一样就可以了

namespace Edmonds_Karp {
#define MAXV 10001
#define MAXE 100001
    int head[MAXV] = {0}, ver[MAXE], val[MAXE], nxt[MAXE], tot = 1;
    void add(int x, int y, int v) {
        ver[++ tot] = y, val[tot] = v, nxt[tot] = head[x], head[x] = tot;
        ver[++ tot] = x, val[tot] = 0, nxt[tot] = head[x], head[x] = tot;
        //构建残量网络 注意残量网络的初始剩余流量为0
    }
    int incf[MAXV], pre[MAXV], vis[MAXV];
    //这次增广的最大流量,前驱
    int s, t;//源点,汇点
    queue<int>q;
    bool bfs() {
        memset(vis, 0, sizeof(vis));
        q.push(s);
        incf[s] = 1e9;//源点无限流量
        register int x;
        while(! q.empty()) {
            x = q.front(), q.pop();
            for(int i = head[x]; i; i = nxt[i])
                if(val[i] && ! vis[ver[i]]) {
                    vis[ver[i]] = 1;
                    pre[ver[i]] = i;
                    incf[ver[i]] = min(incf[x], val[i]);
                    if(ver[i] == t)return 1;
                    q.push(ver[i]);
                }
        }
        return 0;
    }
    int update() {//更新剩余流量
        int p = t, i;
        while(p != s) {
            i = pre[p];
            val[i] -= incf[t];
            val[i ^ 1] += incf[t];
            p = ver[i ^ 1];
        }
        return incf[t];
    }
    int EK() {
        int ans = 0;
        while(bfs()) ans += update();
        return ans;
    }
#undef MAXV
#undef MAXE
}

Dinic

我们可以发现,(EK) 算法每次只更新一条增广路,而一次 (BFS) 可能可以找出多条最短路,这时,我们就可以使用 (Dinic) 算法

每次 (BFS) 的时候我们将图分层,如果存在增广路,我们就在图上 (DFS) 来更新,每次更新的时候都只考虑在下一层的点,回溯的时候再将剩余流量更新

namespace Dinic {
#define MAXV 20001
#define MAXE 200001
    int head[MAXV] = {0}, ver[MAXE], val[MAXV], nxt[MAXV], tot = 1;
    void add(int x, int y, int v) {
        ver[++ tot] = y, val[tot] = v, nxt[tot] = head[x], head[x] = tot;
        ver[++ tot] = x, val[tot] = 0, nxt[tot] = head[y], head[y] = tot;
    }
    int d[MAXV];
    int s, t;
    queue<int>q;
    bool bfs() {
        memset(d, 0, sizeof(d));
        register int x;
        q.push(s);
        d[s] = 1;
        while(!q.empty()) {
            x = q.front(), q.pop();
            for(int i = head[x]; i; i = nxt[i])
                if(val[i] && ! d[ver[i]]) {
                    d[ver[i]] = d[x] + 1;//分层
                    q.push(ver[i]);
                }
        }
        return d[t];
    }
    int dfs(int x, int flow) {//流过这个点的最大可能的流量
        if(x == t) return flow;
        register int k, rest = flow;
        for(int i = head[x]; i && rest; i = nxt[i])
            if(val[i] && d[ver[i]] == d[x] + 1) {
                k = dfs(ver[i], min(val[i], rest));
                if(! k) d[ver[i]] = 0;//剪枝
                rest -= k;
                val[i] -= k;
                val[i ^ 1] += k;
            }
        return flow - rest;
    }
    int dinic() {
        int ans = 0, flow;
        while(bfs()) while((flow = dfs(s, 1e9))) ans += flow;
        return ans;
    }
#undef MAXV
#undef MAXE
}
当前弧优化

(cur) 数组储存 (head) 数组的东西,将 (DFS) 中的 (i) 取引用

for(int &i = head[x]; i; i = nxt[i])

最小割

最大流最小割定理:一个网络中的最大流 = 最小割

证明:假设最大流可能大于最小割,那么在割去这些边后仍可以找到增广路,与源点和汇点不连通矛盾,所以最小割小于等于最大流;如果我们将跑完最大流之后剩余流量为 (0) 的边删去,则无法再找到一条从源点到汇点的路径,否则与不存在增广路相矛盾,于是我们只需要从源点出发,沿着残量网络 (BFS),所有没有被访问过的点与被访问过的点之间的边构成一个割,且这个割的边最大流量和为最大流

以上是关于网络流的主要内容,如果未能解决你的问题,请参考以下文章

此应用小部件片段中所有意图 (PendingIntents) 的逻辑流

使用FFmpeg转录网络直播流

VSCode自定义代码片段14——Vue的axios网络请求封装

VSCode自定义代码片段14——Vue的axios网络请求封装

VSCode自定义代码片段14——Vue的axios网络请求封装

Swift新async/await并发中利用Task防止指定代码片段执行的数据竞争(Data Race)问题