zoukankan      html  css  js  c++  java
  • 网络流

    网络流

    定义

      1,网络流
        用类似水流的定义,可以把一张图视作一堆管道,从s源源不断的流水,看最多有多少水可以汇聚到t (此处的水大致可以视作是每秒一定宽度的管道流过多少水之类的。)。
      2,最大流最小割定理。
        显然如果不把所有可以流的边都堵死就不能算一个最小割,因为还有可以到的边。
        或者说最大流会在一组可以割的边中选最小的那个割掉,因为一组边可以流过的水显然是受容量最小的边所限制的。
      3,必须流和可行流
        1,最小割可行边,
          意思就是最小割中可能出现的边。
          充要条件:
            1,满流
            2,在残余网络中找不到x ---> y的路径
          解释:
            如果在残余网络中还找得到x--->y的路径的话,要割掉这条边就还需要割掉另一条路径,这显然是不够优的。如果是满流的话显然不是割掉了这条边
        2,最小割必须边
          1,满流
          2,在残余网络中s 可以到 x, y 可以到 t。
          解释:
            满流的原因和上面原因一样,同时必须边肯定也是可行边(显然可行边的范围就要大一些嘛)。如果满流但s不能到x or y 不能到 t,因为这样的话说明在s 到 x(y 到 t)的路上就已经被割掉了,而不是在这里割的。但是因为满流了,所以这是可行的,但是由于割在别的地方,说明不是必须的。
    因此s 必须可以到 x, y 必须可以到s才能保证是必须边,而不是可行边
            至于实现方法就比较妙了,如果两个点在一个scc中则表示可以到,因此可行边需要保证x和y不在一个scc中,而必须边则还需要额外保证s 和 x 属于一个scc, y 和 t属于一个scc。

    模板

    这里只放非递归版ISAP。虽然看上去比较长,但好写好调。如果要学习原理的话需要百度?
    稍微熟练一点的话,写起来还是比较快的,十多分钟吧。

    #include<bits/stdc++.h>
    using namespace std;
    #define R register int
    #define LL long long
    #define AC 10100
    #define ac 201000
    
    const int inf = 1e9;
    int n, m, x, s, t, all, addflow, head, tail, ans;
    int Head[AC], date[ac], Next[ac], haveflow[ac], tot = 1;
    int have[AC], good[AC], q[AC], c[AC], last[AC];
    
    inline int read()
    {
        int x = 0;char c = getchar();
        while(c > '9' || c < '0') c = getchar();
        while(c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar();
        return x; 
    }
    
    inline void upmin(int &a, int b) {if(b < a) a = b;}
    inline void upmax(int &a, int b) {if(b > a) a = b;}
    
    inline void add(int f, int w, int S)
    {
        date[++ tot] = w, Next[tot] = Head[f], Head[f] = tot, haveflow[tot] = S;
        date[++ tot] = f, Next[tot] = Head[w], Head[w] = tot, haveflow[tot] = 0;
    }
    
    void bfs()
    {
        q[++ tail] = t, c[t] = have[1] = 1;
        while(head < tail)
        {
            int x = q[++ head];
            for(R i = Head[x]; i; i = Next[i])
                if(!c[date[i]] && haveflow[i ^ 1])
                    have[c[date[i]] = c[x] + 1] ++, q[++ tail] = date[i];
        }
        memcpy(good, Head, sizeof(Head));
    }
    
    void aru()
    {
        while(x != s)
        {
            haveflow[last[x]] -= addflow;
            haveflow[last[x] ^ 1] += addflow;
            x = date[last[x] ^ 1];
        }
        ans += addflow, addflow = inf;
    }
    
    void isap()
    {
        bool done;
        x = s, addflow = inf;
        while(c[s] != all + 1)
        {
            if(x == t) aru();
            done = false;
            for(R &i = good[x]; i; i = Next[i])
            {
                int now = date[i];
                if(c[now] == c[x] - 1 && haveflow[i])
                {
                    upmin(addflow, haveflow[i]), last[now] = i;
                    done = true, x = now; break;
                }
            }	
            if(!done)
            {
                int go = all;
                for(R i = Head[x]; i; i = Next[i])
                    if(c[date[i]] && haveflow[i]) upmin(go, c[date[i]]);
                if(!(--have[c[x]])) break;
                have[c[x] = go + 1] ++, good[x] = Head[x];
                if(x != s) x = date[last[x] ^ 1];
            }
        }	
    }
    
    void pre()
    {
        n = read(), m = read(), s = read(), t = read(), all = n + 3;
        for(R i = 1; i <= m; i ++)
        {
            int x = read(), y = read(), z = read();
            add(x, y, z);
        }
    }
    
    int main()
    {
    //	freopen("in.in", "r", stdin);
        pre();
        bfs();
        isap();
        printf("%d
    ", ans);
    //	fclose(stdin);
        return 0;
    }
    

    常见建模

    1,最小路径覆盖

      定义:用最少的链覆盖全图。
      建图:将每个点分别拆成2个点,一个为出点,一个为入点,建立源点和汇点,分别连接s ---> 出点,入点 ---> t,容量为1,对于图中的每一条边,连出点 ---> 入点。ans = 点数 - 最大流
      理解:这是一个二分图匹配问题,匹配2个点相当于合并2条路径,所以合并路径越多越优。

    2,最小点覆盖 && 最大独立集

    定义:
    在一个二分图中,有如下定义:

    • 最小点覆盖:选出一个点集使得所有边都被覆盖,同时使得点权之和最小;
    • 最大独立集:选出一个点集使得没有边2个点都被覆盖,且点权之和最大。

    结论:最小点覆盖 + 最大独立集 = 总点集
    因为在最小点覆盖中,所有边都被覆盖了,所以取反之后,每条边最多只会选一个端点,因此是一个独立集。
    又由于是最小的点覆盖,所以取反后的独立集也是最大的独立集。

    因此我们给这个二分图增加源汇,然后直接跑最大流。
    就是连:

    • s ---> 奇点 : 点权
    • 偶点 ---> t :点权
    • x ---> y : inf

    于是最大流就保证了每条边肯定都被覆盖了。
    同时因为最大流 = 最小割,所以求出的是最小点覆盖。

    那么如果要求最大独立集的话,用总点数减一减就可以了。

    3,最大权闭合子图

      定义:每个点有点权,要求选出一些点,满足这些点的出边连向的点也必须被选且点权之和最大。
      建图:S向正权点连容量为权值的边,从负权边向t连容量为|权值|的边,对于原图上的边,在图上连容量为inf的边。答案即为:正权值之和 - 最大流
      理解:可以看做先选了所有的点,然后如果割左边的边就是放弃选某些正权点,如果割右边的边就是选某些正权点然后承受相应代价。

    4,带上下界网络流

      1,无源汇带上下界可行流。
        定义一个数组d[x]表示图中点x的入度下限和-出度下限和。
        建图方式为:
          对于图中每一条边,都连流量为上界-下界的边,并在加边的时候统计d[x]。
          对于任意一个点,如果它的d[x] > 0,那么连s --- > x, 流量为d[x];
          如果它的d[x] < 0, 那么连x --- > t, 流量为-d[x]。
          然后直接从s到t跑网络流即可,如果满流即为合法,否则不合法。
          如何理解?
        因为下界是必须达到的,因此先把所有的边都强行达到下界。
        在强行让所有边都达到下界后建出的图不能保证进入流量 = 流出流量,因为我们建图的时候,实际上将多余or少的流量给忽略了,因此我们要再加一些边表示这些被忽略的流量。
        对于任意一个点,如果d[x] > 0,那么表示进来的流量有剩余,还没有流完,但此时图中并没有体现。因此从s给它补充d[x]的流量, 表示x还需要引进额外的d[x]流量。
        反之,如果d[x] < 0,那么表示进来的流量不够用,除此之外还需要额外流出去-d[x]的流量,但此时图中并没有体现。所以向t连边表示点x还需要向t输出-d[x]的流量。
        如果满流,那么代表剩余的流量可以恰好补全不够的流量,那么就是可行了。
        同时因为每条边都变为了上界-下界,因此不管怎么流,都是不会超过上界的,于是就成功的把流量限制在了[下界,上界].
      2,有源汇带上下界可行流。
        建图方式与上述相同,只是把原来的源汇连一条t --- > s : inf的边(现在的超级源汇为ss, tt)
      3,有源汇带上下界最大流。
        先做一遍可行流,然后去掉ss和tt,在残余网络上跑最大流,最大流即为答案。
        因为有反向边,所以之前可行流流出的流量会从反向边流到t,于是基础流量就会被满足了,然后就是在这个基础上增添流量。
        又因为是最大流,所以跑出来的肯定是最优解。
      4,有源汇带上下界费用流
        建图方式和网络流差不多,原图上的边费用不变,新增的边费用为0.一开始减掉下界的时候要加上流下界的费用。
        最后和跑出的费用相加,得到最后的费用。

    5,切糕模型

    问题:给定一个(n imes m)的矩阵,给每个位置填上一个([1, k])之间的数,位置((i, j))(t)可以得到(v(i, j, t))的代价。方案合法当且仅当每个位置的数与相邻的四个位置的数相差不超过(d).求一个最小总代价。

    建图:先忽略相差不超过(d)的限制,考虑这样建图:
    对于每个位置,拆成(k)个点。

    • s ---> (i, j, 1) : v(i, j, 1)
    • (i, j, t - 1) ---> (i, j, t) : v(i, j, t)
    • (i, j, k) ---> t : inf

    于是割掉(i, j, t) ---> (i, j, t + 1)的边就相当于这个位置填(t).
    再考虑如果限制(d)
    切糕.png-21.7kB
    来分析一下这幅图:

    • 对于一个左边的点(x),如果右边取的边比它小(2)以上的话,因为(inf)是割不掉的,因此还会有流量通过这条边将(ge x - 2)的边充满,也就相当于取走了上面的边。
    • 对于一个左边的点(x),如果右边取的边比它大(2)以上的话,假设右边取走了(y (y - x > 2)),那么相对于(y)而言,就相当于左边取走了一个比它小(2)以上的边,与上一种情况冲突,所以不会出现。(这两种情况实际上是站在不同的点上看同一种情况)

    因此这个建图就合法了。

  • 相关阅读:
    Python课程第三天作业
    Python课程第一天作业
    centos7安装Jenkins
    搭建zookeeper+kafka集群
    redis在实践中的一些常见问题以及优化思路
    部署redis4.0-cluster
    redis哨兵架构的基础知识及部署和管理
    Redis主从复制
    部署Redis4.x单机版及配置RDB和AOF持久化
    xshell使用密钥登陆linux
  • 原文地址:https://www.cnblogs.com/ww3113306/p/10651891.html
Copyright © 2011-2022 走看看