Edmonds-Karp算法、Dinic算法、ISAP算法、最大流最小割定理



前言

回家以后学习速度急速下降 QAQ
本文只涉及网络流的基础,真的只有基础…(说难听了叫做只有模板)

  1. 综合
    http://acm.pku.edu.cn/summerschool/gw_netflow.pdf
  2. Dinic
    https://www.cnblogs.com/SYCstudio/p/7260613.html
    https://baike.baidu.com/item/Dinic%E7%AE%97%E6%B3%95/3956790?fr=aladdin
  3. ISAP
    https://www.renfei.org/blog/isap.html
    https://www.cnblogs.com/jffifa/archive/2012/05/14/NetworkFlow.html
    http://mindlee.com/2011/11/19/network-flow/

Flow Problem - HDU 3549

题意
给定n个点,m条边,求从点1到点n的最大流(模板题)
思路
Edmonds-Karp + 邻接矩阵

  // Edmonds-Karp
  #include <cstdio>
  #include <cstring>
  #include <queue>
  using namespace std;
  const int N = 15 + 15;
  const int inf = 0x3f3f3f3f;

  int cap[N][N];
  int flow[N];
  int pre[N];
  queue<int> que;

  int bfs(int n, int src, int des){
      memset(pre, -1, sizeof(pre));
      while(!que.empty())     que.pop();
      flow[src] = inf;
      que.push(src);

      while(!que.empty()){
          int u = que.front();
          que.pop();
          for(int v = 1; v <= n; v++){
              if(v != src && cap[u][v] > 0 && pre[v] == -1){
                  flow[v] = min(cap[u][v], flow[u]);
                  pre[v] = u;
                  que.push(v);
              }
          }
      }
      return pre[des] == -1 ? 0 : flow[des];
  }

  int solve(int n, int src, int des){
      memset(flow, 0, sizeof(flow));
      int sum = 0, aug;
      while((aug = bfs(n, src, des))){
          sum += aug;
          for(int v = des, u = pre[des]; v != src; v = u, u = pre[u]){
              cap[u][v] -= aug;
              cap[v][u] += aug;
          }
      }
      return sum;
  }

  int main(){
      int t, csn = 1;
      scanf("%d", &t);
      while(t--){
          memset(cap[0], 0, sizeof(cap));
          int n, m;
          scanf("%d%d", &n, &m);
          while(m--){
              int u, v, val;
              scanf("%d%d%d", &u, &v, &val);
              cap[u][v] += val;
          }
          printf("Case %d: %d\n", csn++, solve(n, 1, n));
      }
  }

Dinic + 邻接表 + 弧优化

  // Dinic
  #include <cstdio>
  #include <cstring>
  #include <queue>
  using namespace std;
  const int N = 15 + 15;
  const int M = 1000 + 15;
  const int inf = 0x3f3f3f3f;

  struct edge{
      int v, val, nxt;
  };

  int  dpt[N], head[N], cur[N];
  edge e[M << 1];
  int  tot;
  queue<int> que;

  inline void init(){
      memset(head, -1, sizeof(head));
      tot = 0;
  }

  void addEdge(int u, int v, int val){
      e[tot].v   = v;
      e[tot].val = val;
      e[tot].nxt = head[u];
      head[u]    = tot++;
  }

  // 在残留网络中分层,即求出点到源点的距离
  // 最后如果dpt[des]为0,说明不可分层,不存在增广路
  bool getDpt(int src, int des){
      while(!que.empty())     que.pop();
      memset(dpt, 0, sizeof(dpt));
      dpt[src] = 1;
      que.push(src);
      while(!que.empty()){
          int u = que.front();
          que.pop();
          for(int i = head[u]; ~i; i = e[i].nxt){
              int v = e[i].v;
              if(e[i].val > 0 && dpt[v] == 0){
                  dpt[v] = dpt[u] + 1;
                  que.push(v);
              }
          }
      }
      return dpt[des];
  }

  int dfs(int u, int dist, int src, int des){
      if(u == des)      return dist;
      //cur[u]为弧优化,返回时从下一条边开始搜
      for(int& i = cur[u]; ~i; i = e[i].nxt){
          int v = e[i].v;
          if(dpt[v] == dpt[u] + 1 && e[i].val){
              int aug = dfs(v, min(dist, e[i].val), src, des);
              if(aug > 0){
                  e[i].val   -= aug;
                  e[i^1].val += aug;
                  return aug;
              }
          }
      }
      return 0;
  }

  int Dinic(int src, int des){
      int ans = 0;
      while(getDpt(src, des)){
          int aug;
          memcpy(cur, head, sizeof(head));
          while((aug = dfs(src, inf, src, des)))      ans += aug;
      }
      return ans;
  }

  int main(){
      int t, csn = 1;
      scanf("%d", &t);
      while(t--){
          init();
          int n, m;
          scanf("%d%d", &n, &m);
          while(m--){
              int u, v, val;
              scanf("%d%d%d", &u, &v, &val);
              addEdge(u, v, val);
              addEdge(v, u, 0);
          }
          printf("Case %d: %d\n", csn++, Dinic(1, n));
      }
  }

ISAP + 邻接表 + 弧优化 + GAP

// ISAP
  #include <cstdio>
  #include <cstring>
  #include <algorithm>
  using namespace std;
  const int N = 15 + 15;
  const int M = 1e3 + 15;
  const int inf = 0x3f3f3f3f;

  struct edge{
      int v, val, nxt;
  };

  int  d[N], head[N], gap[N], cur[N], pre[N];
  edge e[M << 1];
  int  tot;

  inline void init(){
      memset(head, -1, sizeof(head));
      memset(d, 0, sizeof(d));
      memset(gap, 0, sizeof(gap));
      tot = 0;
  }

  void addEdge(int u, int v, int val){
      e[tot].v   = v;
      e[tot].val = val;
      e[tot].nxt = head[u];
      head[u]    = tot++;
  }

  int ISAP(int n, int src, int des){
      memcpy(cur, head, sizeof(head));
      int sum = 0;
      int u = pre[src] = src;
      gap[0] = n;

      //当src到des无路径时,d[src]必定会大于或等于n
      while(d[src] < n){
          //如果u到达des,则增广
          if(u == des){
              int aug = inf, v;
              for(u = pre[des], v = des; v != src; v = u, u = pre[u])     aug = min(aug, e[cur[u]].val);
              for(u = pre[des], v = des; v != src; v = u, u = pre[u]){
                  e[cur[u]].val   -= aug;
                  e[cur[u]^1].val += aug;
              }
              sum += aug;
              continue;
          }

          //非递归式dfs,寻找可增广路径
          bool flag = false;
          for(int& i = cur[u]; ~i; i = e[i].nxt){
              int v = e[i].v;
              if(d[u] == d[v] + 1 && e[i].val){
                  pre[v] = u;
                  u = v;
                  flag = true;
                  break;
              }
          }

          //若无允许弧,则寻找残留网络中邻接边d最小值,令d[u] = min{...}+1
          //更新GAP
          //回退到上一个点继续搜
          if(!flag){
              int mind = n;
              for(int i = head[u]; ~i; i = e[i].nxt){
                  int v = e[i].v;
                  if(e[i].val && d[v] < mind){
                      mind = d[v];
                      cur[u] = i;
                  }
              }
              if((--gap[d[u]]) == 0)      break;
              d[u] = mind + 1;
              gap[d[u]]++;
              u = pre[u];
          }
      }
      return sum;
  }

  int main(){
      int t, csn = 1;
      scanf("%d", &t);
      while(t--){
          init();
          int n, m;
          scanf("%d%d", &n, &m);
          while(m--){
              int u, v, val;
              scanf("%d%d%d", &u, &v, &val);
              addEdge(u, v, val);
              addEdge(v, u, 0);
          }
          printf("Case %d: %d\n", csn++, ISAP(n, 1, n));
      }
  }


hihoCoder #1378:网络流二·最大流最小割定理

思路
根据定理1,最大流 = 最小割
根据定理3,跑完最大流后再搜一遍在残留网络中S能连接到的点就ok

  #include <cstdio>
  #include <cstring>
  #include <algorithm>
  using namespace std;
  const int N = 500 + 15;
  const int M = 2e4 + 15;
  const int inf = 0x3f3f3f3f;

  struct edge{
      int v, val, nxt;
  };

  int  d[N], head[N], gap[N], cur[N], pre[N], que[N];
  edge e[M << 1];
  int  tot, qhead, qtail;

  inline void init(){
      memset(head, -1, sizeof(head));
      memset(d, 0, sizeof(d));
      memset(gap, 0, sizeof(gap));
      tot = 0;
  }

  void addEdge(int u, int v, int val){
      e[tot].v   = v;
      e[tot].val = val;
      e[tot].nxt = head[u];
      head[u]    = tot++;
  }

  int ISAP(int n, int src, int des){
      memcpy(cur, head, sizeof(head));
      int sum = 0;
      int u = pre[src] = src;
      gap[0] = n;

      while(d[src] < n){
          if(u == des){
              int aug = inf, v;
              for(u = pre[des], v = des; v != src; v = u, u = pre[u])     aug = min(aug, e[cur[u]].val);
              for(u = pre[des], v = des; v != src; v = u, u = pre[u]){
                  e[cur[u]].val   -= aug;
                  e[cur[u]^1].val += aug;
              }
              sum += aug;
              continue;
          }

          bool flag = false;
          for(int& i = cur[u]; ~i; i = e[i].nxt){
              int v = e[i].v;
              if(d[u] == d[v] + 1 && e[i].val){
                  pre[v] = u;
                  u = v;
                  flag = true;
                  break;
              }
          }

          if(!flag){
              int mind = n;
              for(int i = head[u]; ~i; i = e[i].nxt){
                  int v = e[i].v;
                  if(e[i].val && d[v] < mind){
                      mind = d[v];
                      cur[u] = i;
                  }
              }
              if((--gap[d[u]]) == 0)      break;
              d[u] = mind + 1;
              gap[d[u]]++;
              u = pre[u];
          }
      }
      return sum;
  }


  int ans[N], pans;
  bool used[N];
  void getCut(int src){
      memset(used, false, sizeof(used));
      pans = 0;
      qhead = qtail = 0;
      que[qtail++] = src;
      used[src] = true;
      while(qhead != qtail){
          int u = que[qhead++];
          ans[pans++] = u;
          for(int i = head[u]; ~i; i = e[i].nxt){
              int v = e[i].v;
              if(used[v] || e[i].val == 0)         continue;
              que[qtail++] = v;
              used[v] = true;
          }
      }
  }

  int main(){
      int n, m;
      while(~scanf("%d%d", &n, &m)){
          init();
          while(m--){
              int u, v, val;
              scanf("%d%d%d", &u, &v, &val);
              addEdge(u, v, val);
              addEdge(v, u, 0);
          }
          int maxn = ISAP(n, 1, n);
          getCut(1);
          printf("%d %d\n", maxn, pans);
          for(int i = 0; i < pans; i++){
              printf("%d", ans[i]);
              if(i < pans - 1)    putchar(' ');
          }
          puts("");
      }
  }