• 浅谈最大流


    最大流是什么
    \(\quad\)最大流是网络流的一个概念,就是问从源点出发到汇点在保证是一个合法网络流的同时从源点出发的最大流量是多少。
    \(\quad\)既然他是一个合法的网络流,那么就一定要保证以下性质:

    • 除了源点和汇点以外,所有节点的流出量都要等于流入量。
    • 保证每一条管道的流量都小于容量。

    \(\quad\)显然,如果我们用正常的搜索算法入手很难去推断出源点到底输出多少流量才能使满足这两个条件的同时保证最大。但是我们可以考虑一下这个模型:

    • 我们先找出一条路径,得到从源点到汇点经过这条路径的最大流量是多少,那么这很显然可以直接爆搜得到。
    • 我们再去思考能否再有一条路径使得能再增加一点流量。
    • 找到了这条路径之后我们就可以再一次的爆搜,再一次的增流。
    • 重复上方两个步骤,直到无法再次增流的事后,从源点流出的流量就是最大流。

    \(\quad\)我们可以很轻松的发现目前最大的问题是如何找到一条路径使得可以再一次的增流,但是我们可以先考虑一下这么一个性质:

    • 每一次有流量流过管道,管道就需要花费这些容量,那么容量就会相应减少

    \(\quad\)我们就会发现一些玄机,如果我们找到一条合法的路径,那么这条路径当中就一定会有一些管道的容量变成了0,那么它们就不可走了,这时我们如果看做把这些边删掉再去搜索,那么得到的路径就一定会是一条全新的道路,这时我们就可以进行愉快的增流了。
    \(\quad\)在我们就以为这么愉快的结束的时候,我们会发现一些问题:我们如何去进行反悔呢?
    \(\quad\)这时一个很真实的问题,因为有些时候我们无法保证在某个时刻用到这些容量一定会成为最优解,所有说就必然涉及到反悔的问题。那么对于这个问题,我们显然可以想到这么一点:

    • 每一次有流量\(w\)\(u\)流向\(v\),管道\((u,v)\)的容量减去\(w\),但是管道\((v,u)\)的容量增加\(w\)

    \(\quad\)我想这也是一个很显然的性质,当\((u,v)\)有流量通过的时候,\((v,u)\)的流量就会很显然的分为两部分:

    • 来自\((v,u)\)的本身流量。
    • 来自\((u,v)\)的回退流量,就相当于我把一些来自\(u\)的流量返回去,这么做我们显然是可以判断是等效的。

    \(\quad\)那如果\((v,u)\)没有边怎么办呢?当然是自己建一条了!流量是多少呢?当然默认是0了!
    \(\quad\)完成了上方轻松易懂的推论之后我们就可以开始写代码了,代码主要分为三部分:

    Part1 建边

    for(register int i = 1 ; i <= m ; i++){int u = read() , v = read() , w = read() ; add(u , v , w) ; add(v , u , 0);}
    

    Part2 找路径

    inline bool bfs()
    {
      q.push(s) ; memset(dep , 0 , sizeof(dep)) ; dep[s] = 1;
      while(!q.empty())
      {
        int u = q.front() ; q.pop();
        for(register int i = head[u] ; i ; i = net[i])
        {
          int v = vis[i] , w = wigh[i] ; if(w == 0 || dep[v] != 0){continue;}
          dep[v] = dep[u] + 1;
          q.push(v);
        }
      }
      return dep[t];
    }
    

    Part3 爆搜+修改

    int dfs(int u , int in)
    {
      if(u == t){return in;}
      int out = 0;
      for(register int i = head[u] ; i ; i = net[i])
      {
        int v = vis[i] , w = wigh[i];
        if(w != 0 && dep[v] == dep[u] + 1)
        {
          int res = dfs(v , min(w , in));
          wigh[i] -= res;
          wigh[i ^ 1] += res;//注意:此处快捷修改需要将tot初值赋值为1
          in -= res;
          out += res;
        }
      }
      if(out == 0){dep[u] = 0;}
      return out;
    }
    

    \(\quad\)为了防止读者的暴打现在来进行一下代码的讲解:

    • Part1 双向建边应该没有什么好说的,不用担心重边问题,因为都可以等效解决。
    • Part2 在这一段代码中,dep代表的是前驱与后继的关系,因为从一个节点出发访问的子节点有可能被先访问过,所以我们需要用一个\(dep\)来记录。
    • Part3 在这一部分中我们使了一个爆搜+回访的操作,成功把在第二部分找到的路径进行了增流。

    下面给出完整代码

    #include<iostream>
    #include<cstdio>
    #include<cstdlib>
    #include<cstring>
    #include<string>
    #include<algorithm>
    #include<cmath>
    #include<ctime>
    #include<climits>
    #include<sstream>
    using namespace std;
    #include<queue>
    #define int long long
    
    inline int read()
    {
      char c ; int flag = 1;
      while((c = getchar()) < '0' || c > '9'){if(c == '-'){flag = -1;}}
      int count = c - '0';
      while((c = getchar()) >= '0' && c <= '9'){count *= 10 ; count += c - '0';}
      return count * flag;
    }
    
    inline void print(int x)
    {
      if(x < 0){x = -x ; putchar('-');}
      if(x > 9){print(x / 10);}
      putchar(x % 10 + '0');
    }
    
    const int N = 201;
    const int M = 10010;
    
    int head[N] , net[M] , vis[M] , wigh[M] ; int tot = 1;
    inline void add(int u , int v , int w){tot++ ; net[tot] = head[u] ; head[u] = tot ; vis[tot] = v ; wigh[tot] = w;}
    
    int n , m , s , t;
    queue<int> q;
    int dep[N];
    
    inline bool bfs()
    {
      q.push(s) ; memset(dep , 0 , sizeof(dep)) ; dep[s] = 1;
      while(!q.empty())
      {
        int u = q.front() ; q.pop();
        for(register int i = head[u] ; i ; i = net[i])
        {
          int v = vis[i] , w = wigh[i] ; if(w == 0 || dep[v] != 0){continue;}
          dep[v] = dep[u] + 1;
          q.push(v);
        }
      }
      return dep[t];
    }
    
    int dfs(int u , int in)
    {
      if(u == t){return in;}
      int out = 0;
      for(register int i = head[u] ; i ; i = net[i])
      {
        int v = vis[i] , w = wigh[i];
        if(w != 0 && dep[v] == dep[u] + 1)
        {
          int res = dfs(v , min(w , in));
          wigh[i] -= res;
          wigh[i ^ 1] += res;
          in -= res;
          out += res;
        }
      }
      if(out == 0){dep[u] = 0;}
      return out;
    }
    
    signed main()
    {
      n = read() ; m = read() ; s = read() ; t = read();
      for(register int i = 1 ; i <= m ; i++){int u = read() , v = read() , w = read() ; add(u , v , w) ; add(v , u , 0);}
      int ans = 0;
      while(bfs()){ans += dfs(s , 1e18);}
      print(ans);
      return 0;
    }
    

    \(\quad\)其实读者可能会发现作者这些粗劣的解释有很多BUG,但其实如果你仔细想想就会发现很多情况都被等效解决了别问,问就是等效!

  • 相关阅读:
    垃圾回收的可触及性
    常用的垃圾回收算法
    石子归并(区间dp 模板)
    D. Zero Quantity Maximization ( Codeforces Round #544 (Div. 3) )
    Parity game(带权并查集+离散化)
    Supermarket(贪心/并查集)
    D. Nested Segments(树状数组、离散化)
    dijkstra,belllman-ford,spfa最短路算法
    重载符
    Electrification Plan 最小生成树(prim+krusl+堆优化prim)
  • 原文地址:https://www.cnblogs.com/wanwanjiuhao7744/p/13910899.html
Copyright © 2020-2023  润新知