码迷,mamicode.com
首页 > 其他好文 > 详细

网络流

时间:2019-01-03 20:40:28      阅读:173      评论:0      收藏:0      [点我收藏+]

标签:初始   连接   访问   str   line   mon   路径   bfs   引用   

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\),所有没有被访问过的点与被访问过的点之间的边构成一个割,且这个割的边最大流量和为最大流

网络流

标签:初始   连接   访问   str   line   mon   路径   bfs   引用   

原文地址:https://www.cnblogs.com/akakw1/p/10216587.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!