上下界网络流、最小费用最大流



前言

(点了首这么悲伤的BGM,见谅)
回到学校就把系统玩崩了(挂滚有线网卡了),于是从Manjaro跳到Ubuntu T^T
然后又被催更了 T^T

  1. 综合:
    http://acm.pku.edu.cn/summerschool/gw_netflow.pdf
  2. 有上下界的网络流
    https://blog.csdn.net/YxuanwKeith/article/details/52238928
    https://www.cnblogs.com/kane0526/archive/2013/04/05/3001108.html
    https://www.cnblogs.com/liu-runda/p/6262832.html
    https://blog.csdn.net/Hanks_o/article/details/77995557
    https://blog.csdn.net/blue_cuso4/article/details/78921497
    https://dozbear.com/bounded-network-flow-review/index.html
  3. 最小费用最大流
    https://riteme.github.io/blog/2016-2-2/mincost-maxflow.html


Reactor Cooling - SGU 194

题意
给定无源汇上下界网络求可行流
思路
模板题

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

  int  pre[N], d[N], cur[N], gap[N];
  int  cap[N][N], flow[N][N], lower_flow[N][N];
  int  u[N*N], v[N*N];

  inline void init(){
      memset(cap[0], 0, sizeof(cap));
      memset(flow[0], 0, sizeof(flow));
  }

  inline void addEdge(int u, int v, int val){
      cap[u][v] += val;
      cap[v][u] += val;
      flow[v][u] += val;
  }

  int ISAP(int src, int des, int n){
      memset(cur, 0, sizeof(cur));
      memset(gap, 0, sizeof(gap));
      memset(d, 0, sizeof(d));
      int u = pre[src] = src;
      int sum = 0;
      gap[0] = n;
      while(d[src] < n){
          if(u == des){
              int v, aug = inf;
              for(u = pre[des], v = des; v != src; v = u, u = pre[u])     aug = min(aug, cap[u][v] - flow[u][v]);
              for(u = pre[des], v = des; v != src; v = u, u = pre[u]){
                  flow[u][v] += aug;
                  flow[v][u] -= aug;
              }
              sum += aug;
              continue;
          }

          bool flag = false;
          for(int& v = cur[u]; v < n; v++){
              if(u == v)  continue;
              if(d[u] == d[v] + 1 && cap[u][v] - flow[u][v]){
                  pre[v] = u;
                  u = v;
                  flag = true;
                  break;
              }
          }

          if(!flag){
              int mind = n;
              for(int v = 0; v < n; v++){
                  if(u == v)  continue;
                  if(cap[u][v] - flow[u][v] && d[v] < mind){
                      mind = d[v];
                      cur[u] = v;
                  }
              }

              if((--gap[d[u]]) == 0)    break;
              d[u] = mind + 1;
              gap[d[u]]++;
              u = pre[u];
          }
      }
      return sum;
  }

  int main(){
      int n, m;
      while(~scanf("%d%d", &n, &m)){
          init();
          int sum = 0;
          for(int i = 1; i <= m; i++){
              int l, r;
              scanf("%d%d%d%d", &u[i], &v[i], &l, &r);
              addEdge(u[i], v[i], r - l);
              addEdge(u[i], 0, l);            // 0为超级汇点,n+1为超级源点
              addEdge(n + 1, v[i], l);
              lower_flow[u[i]][v[i]] = l;
              sum += l;                       // 统计下界流
          }

          if(sum <= ISAP(n + 1, 0, n + 2)){   // 可行流 >= 下界流,则ok
              puts("YES");
              for(int i = 1; i <= m; i++){
                  printf("%d\n", flow[u[i]][v[i]] + lower_flow[u[i]][v[i]]);  //边中的流量 + 下界的流量
              }
          }else{
              puts("NO");
          }
      }
  }


Shoot the Bullet - ZOJ 3229

题意
本题题意改自网络
一个人给m个女神拍照,计划拍照n天,每一天屌丝给给定的C个女神拍照,每天拍照数不能超过D张,而且给每个女神i拍照有数量限制[Li,Ri],对于每个女神n天的拍照总和不能少于Gi,如果有解求最多能拍多少张照,并求每天给对应女神拍多少张照;否则输出-1
思路
首先是建图,设一源点s和汇点t,引入m个节点代表每个女神,从这m个节点各自引出一条[Gi, inf]边连接,再加入n个节点表示每一天,从s到每一天的节点各连接一条[0, D]的边,最后连接m个女神节点和n个天的节点之间[Li, Ri]的边,再引入超级源点和超级汇点作等价转化
从t到s连接一条inf的边从ss到tt跑最大流,再从s到t跑一遍最大流,即得到最终的最大流

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

  struct edge{
      int v, val, cap, next;
      edge(int pv, int pval, int pcap, int pnext): v(pv), val(pval), cap(pcap), next(pnext) {}
      edge() {}
  };

  edge e[M];
  int  tot, head[N], pre[N], d[N], cur[N], gap[N];
  int  anse[M], pans;

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

  inline void addEdge(int u, int v, int val){
      e[tot] = edge(v, 0, val, head[u]);
      head[u] = tot++;
      e[tot] = edge(u, val, val, head[v]);
      head[v] = tot++;
  }

  int ISAP(int src, int des, int n){
      memcpy(cur, head, sizeof(head));
      memset(gap, 0, sizeof(gap));
      memset(d, 0, sizeof(d));
      int u = pre[src] = src;
      int sum = 0;
      gap[0] = n;
      while(d[src] < n){
          if(u == des){
              int v, aug = inf;
              for(u = pre[des], v = des; v != src; v = u, u = pre[u])     aug = min(aug, e[cur[u]].cap - 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].next){
              int v = e[i].v;
              if(d[u] == d[v] + 1 && e[i].cap - 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].next){
                  int v = e[i].v;
                  if(e[i].cap - 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 n, m;
      while(~scanf("%d%d", &n, &m)){
          init();
          int tt = n + m + 2, ss = n + m + 3;
          int s = 0, t = n + m + 1;
          int sum = 0;
          for(int i = 1; i <= m; i++){
              int val;
              scanf("%d", &val);
              addEdge(n + i, tt, val);
              addEdge(ss, t, val);
              addEdge(n + i, t, inf);
              sum += val;
          }
          for(int i = 1; i <= n; i++){
              int k, val;
              scanf("%d%d", &k, &val);
              addEdge(s, i, val);
              while(k--){
                  int idx, l, r;
                  scanf("%d%d%d", &idx, &l, &r);
                  addEdge(i, tt, l);
                  anse[pans++] = tot - 2;
                  addEdge(ss, n + 1 + idx, l);
                  addEdge(i, n + 1 + idx, r - l);
                  anse[pans++] = tot - 2;
                  sum += l;
              }
          }
          addEdge(t, s, inf);

          int ans = ISAP(ss, tt, n + m + 4);
          if(sum <= ans){
              //不用 += 是因为上一次的结果贮存在t到s这条边中,再跑一次最大流会利用这个结果
              ans = ISAP(s, t, n + m + 4);
              printf("%d\n", ans);
              for(int i = 0; i < pans; i += 2){
                  printf("%d\n", e[anse[i]].val + e[anse[i + 1]].val);
              }
          }else{
              puts("-1");
          }
          puts("");
      }
  }


Flow construction - SGU 176

题意
给定网络,输入中每条边的最后一个数若为0,则表示为[0, val],若为1,则表示为[val, val],求该网络从1到N的最小流
思路
模板题
进行等价转换后,先跑一遍ss到tt的最大流,连上t到s的inf边,再跑一遍ss到tt的最大流,最终t->s边中的流量即是答案
(具体原因个人不是很懂,望有dalao能指点指点)

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

  struct edge{
      int v, val, cap, next;
      edge(int pv, int pval, int pcap, int pnext): v(pv), val(pval), cap(pcap), next(pnext) {}
      edge() {}
  };

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

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

  inline void addEdge(int u, int v, int val){
      e[tot] = edge(v, 0, val, head[u]);
      head[u] = tot++;
      e[tot] = edge(u, val, val, head[v]);
      head[v] = tot++;
  }

  int ISAP(int src, int des, int n){
      memcpy(cur, head, sizeof(head));
      memset(gap, 0, sizeof(gap));
      memset(d, 0, sizeof(d));
      int u = pre[src] = src;
      int sum = 0;
      gap[0] = n;
      while(d[src] < n){
          if(u == des){
              int v, aug = inf;
              for(u = pre[des], v = des; v != src; v = u, u = pre[u])     aug = min(aug, e[cur[u]].cap - 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].next){
              int v = e[i].v;
              if(d[u] == d[v] + 1 && e[i].cap - 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].next){
                  int v = e[i].v;
                  if(e[i].cap - 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 n, m;
      while(~scanf("%d%d", &n, &m)){
          init();
          int s = 1, t = n, ss = 0, tt = n + 1;
          int sum = 0;
          while(m--){
              int u, v, val, type;
              scanf("%d%d%d%d", &u, &v, &val, &type);
              if(type){
                  addEdge(u, tt, val);
                  addEdge(ss, v, val);
                  sum += val;
              }else{
                  addEdge(u, v, val);
              }
              anse[pans++] = tot - 2;
          }
          bool flag = true;
          ISAP(ss, tt, n + 2);
          addEdge(t, s, inf);
          ISAP(ss, tt, n + 2);
          for(int i = head[ss]; ~i; i = e[i].next){
              if(e[i].cap - e[i].val){
                  flag = false;
                  break;
              }
          }
          if(flag){
              printf("%d\n", e[tot - 2].val);
              for(int i = 0; i < pans; i++){
                  printf("%d", e[anse[i]].val);
                  if(i < pans - 1)    putchar(' ');
              }
              puts("");
          }else{
              puts("Impossible");
          }
      }
  }


Farm Tour - POJ 2135

题意
在给定无向图中,求出从1到N,再从N到1,且不走重复路径的最短路径
思路
最小费用最大流模板题
设边的费用为距离,流量为1,引入超级源点ss连向1,超级汇点tt,n连向tt,流量均为2,费用均为0,跑一遍最大流即可
本题个人觉得非常巧妙地运用了最大流来避免走重复路径

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

  struct edge{
      int v, val, cost, next;
      edge(int pv, int pval, int pcost, int pnext): v(pv), val(pval), cost(pcost), next(pnext) {}
      edge() {}
  };

  edge e[M << 2];
  int  tot, head[N], pre[N], d[N], cur[N];
  bool inq[N];
  queue<int> que;

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

  inline void addEdge(int u, int v, int val, int cost){
      e[tot] = edge(v, val, cost, head[u]);
      head[u] = tot++;
      e[tot] = edge(u, 0, -cost, head[v]);
      head[v] = tot++;
  }

  bool spfa(int src, int des){
      memset(d, 0x3f, sizeof(d));
      memset(inq, false, sizeof(inq));
      d[src] = 0, pre[src] = src;
      que.push(src);
      inq[src] = true;
      while(!que.empty()){
          int u = que.front();
          que.pop();
          inq[u] = false;
          for(int i = head[u]; ~i; i = e[i].next){
              int v = e[i].v;
              if(d[v] > d[u] + e[i].cost && e[i].val){
                  d[v] = d[u] + e[i].cost;
                  pre[v] = u;
                  cur[v] = i;
                  if(!inq[v]){
                      que.push(v);
                      inq[v] = true;
                  }
              }
          }
      }
      return d[des] != inf;
  }

  int solve(int src, int des){
      int ans = 0;
      while(spfa(src, des)){
          int aug = inf;
          for(int v = des; v != src; v = pre[v])     aug = min(aug, e[cur[v]].val);
          for(int v = des; v != src; v = pre[v]){
              e[cur[v]].val -= aug;
              e[cur[v]^1].val += aug;
          }
          ans += d[des] * aug;
      }
      return ans;
  }

  int main(){
      int n, m;
      while(~scanf("%d%d", &n, &m)){
          init();
          int ss = 0, tt = n + 1, s = 1, t = n;
          while(m--){
              int u, v, cost;
              scanf("%d%d%d", &u, &v, &cost);
              addEdge(u, v, 1, cost);
              addEdge(v, u, 1, cost);
          }
          addEdge(ss, s, 2, 0);
          addEdge(t, tt, 2, 0);
          printf("%d\n", solve(ss, tt));
      }
      return 0;
  }