网络流

网络流

定义

  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。虽然看上去比较长,但好写好调。如果要学习原理的话需要百度?
----update in 2021/9/20---- 近期会更新一下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\n", 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 \times 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

----update in 2021/9/19----不知道为什么这幅图不能正常显示,,,找个机会修修吧
来分析一下这幅图:

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

因此这个建图就合法了。

posted @ 2019-04-03 22:05  ww3113306  阅读(216)  评论(0编辑  收藏  举报
知识共享许可协议
本作品采用知识共享署名-非商业性使用-禁止演绎 3.0 未本地化版本许可协议进行许可。