zoukankan      html  css  js  c++  java
  • [知识点]网络流基础

    零、目录

      I、网络流基础

      II、网络流进阶之转换对偶图

      III、网络流进阶之费用流

    一、前言

    这是ACM之路的第一篇文章,是在通过看自己OI生涯的文章来回顾知识点的过程中,实在难以接受当时过于含糊笼统的介绍的情况下决定开写的,真是对不住1300+的阅读量了。由于网络流的EK算法和Dinic算法就是早期的知识点系列文章,当时确实疏漏很多,现在通过我目前残缺的知识框架和基本功重新整理一下。

    网络流是一个很广泛的问题,是一种类比水流的解决方案。从算法角度来看,最常见的基础算法为Dinic算法,也是本文最重要的部分。从问题角度而言,对于算法竞赛,最常见的问题莫过于最大流问题。何为最大流问题,我们先通过一道例题的题面来体会。

    我们以图片的形式来简化题干要求。

    设图中存在4个节点,其中1号为水潭,即水流的源点,4号为小溪,即水流的汇点,2、3号为其余的交叉点。节点之间存在带权排水沟,其权值表示水流在这条沟中的最大流量。任务就是从1号节点至4号节点最多能有多大的水流量。

    先轻松地人脑分析一下——由图可知,从1号至4号可流20;从1号至2号虽然可流40,但2号的所有可流出的沟中总流量只有30;其中直接至4号的流量为20;至3号的流量为10,原因是3号至4号也只可流10。综上,总流量为20+30=50。

    二、最大流问题

    上述题便是总经典的最大流问题的模板题。不难有一种思路——每次对图进行搜索,寻找一条从源点通向汇点的路,并记录这条路上的最小流量,将每一条支路均减去该流量即可。如果你已经懂了,恭喜你你已经轻而易举地掌握了所谓的Edmon-Karp算法。天哪网络流简直不要太好理解???那我们再来看一张图:

    来来来告诉我最大流为多少?

    第一条路:1-2-3-4,流量为10;第二条路:1-6-5-4,流量为10。总流量20。完美。

    但我们假设一种情况——如果计算机先选择了1-2-5-6这条路?从原理上来看,显然没有毛病;但是一旦1-2-5-6被选择,这就意味着——接下来并没有流量可以流了,而结果也就停留在了10,显然与答案不符。

    什么鬼???

    也就是说,之前的策略显然不是最优的,而我们已不能操纵程序改变已选择的路——自己走的路跪着都要走完。时至如今,有什么反悔药可以吃嘛?有啊。

    我们先给每条路开个后门——设置一条流量为0的反向的路,称作反向弧

    如上,我们现在选择了1-2-5-6,在给1-2,2-5,5-6三条路流量清零的同时,给2-1,5-2,6-5这三条反向弧增加10流量。在没有反向弧的时候可以发现并没有路可走了,但现在我们可以另辟蹊径选择1-6-5-2-3-4:

    这样,最大流求得20,即答案。

    反向弧的作用是:在当前存在更好选择的时候会使之前选择的弧会被撤销。反向弧之所以有流量,是因为刚刚选择了该反向弧的正向弧,而下一次选择的时候如果经过该反向弧,则该弧正反向抵消,起到反悔作用。

    三、Dinic算法

    前面所述的Edmon-Karp算法好理解——但如此一遍遍的全图跑DFS,时间复杂度可想而知。在正确性落实后,我们考虑进行一定优化提高效率。是时候搬出Dinic算法了。

    Dinic算法的核心在于:

    1、对图进行BFS,绘制出层次图,即对每一个节点进行标记,记录其与源点的距离,称为dep深度。如第一张图所示,dep[] = {0, 0, 2, 2, 1};

    2、对图进行DFS,过程基本同EK算法,但在搜索过程中,只遍历至当前节点深度+1的节点

    3、反复进行1、2操作,直至第1步层次图中汇点dep值为空,即没有路可抵达汇点,表示最大流已求出,即完成任务。

    四、例题与代码

    题干见上。

    Dinic算法代码:

    #include <cstdio>
    #include <cstring>
    
    #define MAXN 205
    #define MAXM 205
    #define INF 0x3f3f3f3f
    
    int m, n, u, v, f, q[MAXN * MAXN], h[MAXN], d[MAXN], o = 1, ans;
    
    struct Edge {
        int v, next, f;
    } e[MAXM << 1];
    
    int min(int a, int b) {
        return a < b ? a : b;
    }
    
    void add(int u, int v, int f) {
        o++, e[o] = (Edge) {v, h[u], f}, h[u] = o;
    }
    
    bool BFS() {
        int head = 1, tail = 2;
        q[1] = 1, d[1] = 1;
        while (head != tail) {
            int o = q[head];
            for (int x = h[o]; x; x = e[x].next) {
                int v = e[x].v;
                if (!d[v] && e[x].f > 0) d[v] = d[o] + 1, q[tail++] = v;
            }
            head++;
        }
        return d[n] != 0;
    }
    
    int DFS(int o, int mif) {
        int res = 0;
        if (mif <= 0 || o == n) return mif;
        for (int x = h[o]; x; x = e[x].next) {
            int v = e[x].v;
            if (d[v] == d[o] + 1) {
                int of = DFS(v, min(mif, e[x].f));
                e[x].f -= of, e[x ^ 1].f += of, mif -= of, res += of;
                if (!mif) break;
            }
        }
        return res;
    }
    
    int main() {
        scanf("%d %d", &m, &n);
        for (int i = 1; i <= m; i++) scanf("%d %d %d", &u, &v, &f), add(u, v, f), add(v, u, 0);
        while (BFS()) ans += DFS(1, INF), memset(d, 0, sizeof(d)); 
        printf("%d", ans);
        return 0;
    }

    五、后记

    后续将跟进费用流等更深入的问题。

  • 相关阅读:
    aspx页面按钮写返回上一页代码
    Javascript呼叫.axd文档
    获取GridView TemplateField的数据
    对象失去焦点时自己动提交数据 V2
    从图片路径获取图片尺寸
    双击一个图片然后跳转到另一个页面去
    Javascript alert消息换行
    ASP.NET播放Flash(.SWF)视频
    绑定List<T>到asp:Table控件
    Linux系统下的多线程编程条件变量&信号量
  • 原文地址:https://www.cnblogs.com/jinkun113/p/9427825.html
Copyright © 2011-2022 走看看