网络流之最大流(FF, EK, Dinic, SAP)

来源:互联网 发布:宜家衣柜 知乎 编辑:程序博客网 时间:2024/06/18 12:35

首先是一些性质:

· 最大流等于最小割

· 平面图的最大流等于其对偶图的最短路(暂时未遇到过题)

· 二分图的最大匹配可通过最大流去求 [对于一个二分图,令已有的边的容量(Capacity)为无穷大,增加一个源点s和一个汇点t,令s和t分别连接二部图中的一个分部,并设置其容量为1。这时得到流网络G,计算得到的最大流就等于最大二分匹配。]


模板以POJ-1273为例,有下面四种方法可以解决。


1.FORD-FULKERSON(FF)

相当于dfs找任意增广路的算法,时间复杂度为 O(V·E^2),时间效率较低;

#include <algorithm>#include <iostream>#include <string.h>#include <cstdio>#include <stack>using namespace std;const int inf = 0x7fffffff;const int maxn = 205;struct node{int v, w, next;}edge[maxn*maxn];int no, n, m;int head[maxn], pre[maxn], rec[maxn], flow[maxn];stack<int> stk;void init(){no = 0;memset(head, -1, sizeof head);}//静态邻接表存边 void add(int u, int v, int w){edge[no].v = v; edge[no].w = w;edge[no].next = head[u]; head[u] = no++;edge[no].v = u; edge[no].w = 0;edge[no].next = head[v]; head[v] = no++;}int dfs(int S, int T){memset(pre, -1, sizeof pre);while(!stk.empty()) stk.pop();pre[S] = S; flow[S] = inf;stk.push(S);while(!stk.empty())//用栈迭代替代dfs深搜 {int top = stk.top(); stk.pop();int k = head[top];while(k != -1){if(pre[edge[k].v] == -1 && edge[k].w > 0){flow[edge[k].v] = min(flow[top], edge[k].w);pre[edge[k].v] = top;rec[edge[k].v] = k;stk.push(edge[k].v);}k = edge[k].next;}if(pre[T] != -1) return flow[T];}return -1;}int FF(int s, int t){int ans = 0, add;while((add = dfs(s, t)) != -1)//直到找不到增广路 {ans += add;int k = t;while(k != s){edge[rec[k]].w -= add;edge[rec[k]^1].w += add;k = pre[k];}}return ans;}int main(){ios::sync_with_stdio(0);int u, v, w;while(cin >> m >> n) {init();for(int i = 0; i < m; ++i){cin >> u >> v >> w;add(u, v, w); }cout << FF(1, n) << endl;}return 0;}

2.Edmonds-Karp(EK)

相当于用bfs代替dfs找任意增广路的算法,时间复杂度为 O(V·E^2),时间效率较低;

#include <algorithm>#include <iostream>#include <string.h>#include <cstdio>#include <queue>using namespace std;const int inf = 0x7fffffff;const int maxn = 205;struct node{int v, w, next;}edge[maxn*maxn];int no, n, m;int head[maxn], pre[maxn], rec[maxn], flow[maxn];queue<int> q;void init(){no = 0;memset(head, -1, sizeof head);}//静态邻接表存边 void add(int u, int v, int w){edge[no].v = v; edge[no].w = w;edge[no].next = head[u]; head[u] = no++;edge[no].v = u; edge[no].w = 0;edge[no].next = head[v]; head[v] = no++;}int dfs(int S, int T){memset(pre, -1, sizeof pre);while(!q.empty()) q.pop();pre[S] = S; flow[S] = inf;q.push(S);while(!q.empty()){int top = q.front(); q.pop();int k = head[top];while(k != -1){if(pre[edge[k].v] == -1 && edge[k].w > 0){flow[edge[k].v] = min(flow[top], edge[k].w);pre[edge[k].v] = top;rec[edge[k].v] = k;q.push(edge[k].v);}k = edge[k].next;}if(pre[T] != -1) return flow[T];}return -1;}int EK(int s, int t){int ans = 0, add;while((add = dfs(s, t)) != -1)//直到找不到增广路 {ans += add;int k = t;while(k != s){edge[rec[k]].w -= add;edge[rec[k]^1].w += add;k = pre[k];}}return ans;}int main(){ios::sync_with_stdio(0);int u, v, w;while(cin >> m >> n) {init();for(int i = 0; i < m; ++i){cin >> u >> v >> w;add(u, v, w); }cout << EK(1, n) << endl;}return 0;}

3.Dinic

预处理出层次图,然后再层次图上进行增广,时间复杂度为 O(V^2·E),时间效率较好;

#include <algorithm>#include <iostream>#include <string.h>#include <cstdio>#include <queue>using namespace std;const int inf = 0x7fffffff;const int maxn = 205;struct node{int v, w, next;}edge[maxn*maxn];int dis[maxn], pre[maxn], rec[maxn], head[maxn], block[maxn];int n, m, no;queue<int> q;//静态邻接表存边 inline void add(int u, int v, int w){edge[no].v = v; edge[no].w = w;edge[no].next = head[u]; head[u] = no++;edge[no].v = u; edge[no].w = 0;edge[no].next = head[v]; head[v] = no++;}inline void pre_init(){no = 0;memset(head, -1, sizeof head);}void init(int S, int T){//初始化一定要注意把所涉及的都覆盖到 for(int i = 0; i <= n; ++i){ dis[i] = inf;block[i] = 0;//标记阻塞点 } while(!q.empty()) q.pop();dis[S] = 0; q.push(S);while(!q.empty())//生成层次图 {int tp = q.front(); q.pop();int k = head[tp];while(k != -1){if(dis[edge[k].v] == inf && edge[k].w){dis[edge[k].v] = dis[tp] + 1;q.push(edge[k].v);}k = edge[k].next;}}}int dinic(int S, int T){int top = S, ans = 0, flow = inf;pre[S] = S; init(S, T);while(dis[T] != inf)//当S无法到达T,不能再增广了 {int k = head[top];while(k != -1){if(edge[k].w && dis[edge[k].v] == dis[top]+1 && !block[edge[k].v]) break;k = edge[k].next;}if(k != -1)//找到下一节点 {int v = edge[k].v;flow = min(flow, edge[k].w);pre[v] = top; rec[v] = k;top = v;if(top == T){ans += flow;v = -1; k = T;while(k != S){edge[rec[k]].w -= flow;edge[rec[k]^1].w += flow;if(!edge[rec[k]].w) v = k;//寻找距S最近的一个"瓶颈"边 k = pre[k];}flow = inf;//此处flow必须在外面,大佬的板子可能没注意到,我认为是必须的 if(v != -1)//找到"瓶颈"边 {top = pre[v];k = top;while(k != S){flow = min(edge[rec[k]].w, flow);k = pre[k];}}}}else{block[top] = 1;//找不到下一节点成为阻塞点 top = pre[top];//回溯 if(block[S]) init(S, T);//如果S被阻塞,重新计算层次图//阻塞点的产生也造成了flow的最小值可能是后面的值,虽然进行一次//增广并没什么问题,但上述寻找瓶颈边的判断则是必须的了。 }}return ans;}int main(){ios::sync_with_stdio(0);int u, v, w;while(cin >> m >> n){pre_init();for(int i = 1; i <= m; ++i){cin >> u >> v >> w;add(u, v, w);}cout << dinic(1, n) << endl;}return 0;}/**********************比赛版本***********************/#include <algorithm>#include <iostream>#include <string.h>#include <cstdio>#include <queue>using namespace std;const int inf = 0x3f3f3f3f;const int maxn = 205;const int maxm = maxn*maxn;struct node{int w; int v, next;} edge[maxm];int pre[maxn], rec[maxn], head[maxn], block[maxn];int dis[maxn];int n, m, no;int S, T;queue<int> q;inline void init(){no = 0;memset(head, -1, sizeof head);}inline void add(int u, int v, int w){edge[no].v = v; edge[no].w = w;edge[no].next = head[u]; head[u] = no++;edge[no].v = u; edge[no].w = 0;edge[no].next = head[v]; head[v] = no++;}void reset(int S, int T){memset(dis, 0x3f, sizeof dis);memset(block, 0, sizeof block);q.push(S); dis[S] = 0;while(!q.empty()){int top = q.front(); q.pop();for(int k = head[top]; k != -1; k = edge[k].next)if(dis[edge[k].v] == inf && edge[k].w)dis[edge[k].v] = dis[top]+1, q.push(edge[k].v);}}int dinic(int S, int T){int ans = 0, flow = inf;int top = S;reset(S, T); pre[S] = S;while(dis[T] != inf){int k, tmp;for(k = head[top]; k != -1; k = edge[k].next){if(edge[k].w && dis[edge[k].v]==dis[top]+1 && !block[edge[k].v]) break;}if(k != -1){tmp = edge[k].v;flow = min(flow, edge[k].w);pre[tmp] = top, rec[tmp] = k;top = tmp;if(top == T){ans += flow; tmp = -1;for(; top != S; top = pre[top]){edge[rec[top]].w -= flow;edge[rec[top]^1].w += flow;if(!edge[rec[top]].w) tmp = top;}flow = inf;if(tmp != -1){top = pre[tmp];for(; top != S; top = pre[top])flow = min(flow, edge[rec[top]].w);top = pre[tmp];}}}else{block[top] = 1;top = pre[top];if(block[S]) reset(S, T);}}return ans;}void mapping(){int u, v, w;for(int i = 1; i <= m; ++i){scanf("%d %d %d", &u, &v, &w);add(u, v, w);}}int main(){while(~scanf("%d %d", &m, &n)){S = 1, T = n;init();mapping();printf("%d\n", dinic(S, T));}return 0;}


4.Shortest Augmenting Paths(SAP)

顾名思义,就是找最短的增广路,把EK算法的O(E)时间优化到了O(V),故时间复杂度为 O(V^2·E),加了当前弧优化和间隙优化后的SAP时间效率非常好。

对于Gap优化成立的解释:
假如某次修改d[u]后第一次出现断层k,显然d[u]之前是等于k的,而d[u]修改的原因是修改前d[u] < d[v]+1,所以d[v] > k-1,而因为出现断层k,d[v] != k,故d[v] 〉 k。而由于d函数时单调递增的,所以这个空缺永远不会被填补。而你可能会想会不会存在存在若干个层次k-1的点和层次k+1的点能填补这个空缺?答案是否定的,因为假如存在层次k-1的点和层次k+1的点连边,那么层次k+1的这个点的存在是不合适的,因为不管是初始时候或者是之后修改层次的原因,层次k+1的这个点应该是以层次k存在,而发生断层不存在k层次,所以反证得这个空缺永远填不上。

#include <algorithm>  #include <iostream>  #include <string.h>  #include <cstdio>  #include <queue>  using namespace std;  const int inf = 0x7fffffff;  const int maxn = 205;  const int maxm = maxn*maxn; //一定要好好计算边的数量   struct node  {      int v, w, next;  }edge[maxm];  int dis[maxn], pre[maxn], rec[maxn], head[maxn], gap[maxn], now[maxn];  int n, m, no, up;  //up指逆层次图可能还有增广路时dis的上界 queue<int> q;  //静态邻接表存边   inline void add(int u, int v, int w)  {      edge[no].v = v; edge[no].w = w;      edge[no].next = head[u]; head[u] = no++;      edge[no].v = u; edge[no].w = 0;      edge[no].next = head[v]; head[v] = no++;  }  inline void pre_init()  {      no = 0;  up = n;    memset(head, -1, sizeof head);  }  void init(int S, int T)  {    for(int i = 0; i <= up; ++i)       {    now[i] = head[i];   //now用作当前弧的优化       //注意这里now数组要把所有用到的标号都存过来      gap[i] = 0, dis[i] = inf;    //初始化一定要注意把所涉及的都覆盖到   }    while(!q.empty()) q.pop();      dis[T] = 0; q.push(T);      while(!q.empty())   //生成逆层次图       {          int tp = q.front(); q.pop();          ++gap[dis[tp]];          int k = head[tp];          while(k != -1)          {              if(dis[edge[k].v] == inf && edge[k^1].w)              {                  dis[edge[k].v] = dis[tp]+1;                  q.push(edge[k].v);              }              k = edge[k].next;          }      }  }  int SAP(int S, int T)  {      int ans = 0, flow = inf, top = S;      pre[S] = S; init(S, T);      while(dis[S] < up)    //当S到T的距离大于等于点的个数时肯定就不能再增广了       {                   //切记此处与节点数比较,因为通过方向变会造成距离可能达到节点数          if(top == T)          {              ans += flow;              while(top != S) //修改残留网络,并置top为S               {                  edge[rec[top]].w -= flow;                  edge[rec[top]^1].w += flow;                  top = pre[top];              }              flow = inf;          }          int k = now[top];          while(k != -1)          {              int v = edge[k].v;              if(edge[k].w && dis[top] == dis[v]+1)              {                  flow = min(flow, edge[k].w);                  pre[v] = top; rec[v] = k;                  now[top] = k;//当前弧的优化                   top = v;                  break;              }              k = edge[k].next;          }          if(k == -1)          {              int mind = n;              if(--gap[dis[top]] == 0) break;//出现断层,间隙优化               int k = now[top] = head[top];//改变当前点的距离标号,也要清除之前的当前弧优化的影响               while(k != -1)              {                  if(edge[k].w && mind>dis[edge[k].v]) mind = dis[edge[k].v];                  k = edge[k].next;              }              ++gap[dis[top] = mind+1];              top = pre[top];//回溯到上一个点           }      }      return ans;  }int main()  {    ios::sync_with_stdio(0);int u, v, w;while(cin >> m >> n){pre_init();for(int i = 1; i <= m; ++i){cin >> u >> v >> w;add(u, v, w);}cout << SAP(1, n) << endl;}    return 0;  }/**********************比赛版本***********************/#include <algorithm>  #include <iostream>  #include <string.h>  #include <cstdio>  #include <queue>  using namespace std;  const int inf = 0x3f3f3f3f;  const int maxn = 205;  const int maxm = maxn*maxn;struct node{int w; int v, next;} edge[maxm];  int pre[maxn], rec[maxn], head[maxn], gap[maxn], now[maxn];  int dis[maxn];int n, m, no, up;  int S, T; queue<int> q;inline void add(int u, int v, int w)  {      edge[no].v = v; edge[no].w = w;      edge[no].next = head[u]; head[u] = no++;      edge[no].v = u; edge[no].w = 0;      edge[no].next = head[v]; head[v] = no++;  }  inline void pre_init()  {      no = 0;    memset(head, -1, sizeof head);  }  void init(int S, int T)  {      memset(gap, 0, sizeof gap);      memset(dis, 0x3f, sizeof dis);      for(int i = 0; i <= up; ++i)       now[i] = head[i];    while(!q.empty()) q.pop();      dis[T] = 0; q.push(T);      while(!q.empty())     {          int tp = q.front(); q.pop();          ++gap[dis[tp]];          int k = head[tp];          while(k != -1)          {              if(dis[edge[k].v] == inf && edge[k^1].w)              {                  dis[edge[k].v] = dis[tp]+1;                  q.push(edge[k].v);              }              k = edge[k].next;          }      }  }  int SAP(int S, int T)  {    int ans = 0, flow = inf;int top = S;      pre[S] = S; init(S, T);      while(dis[S] < up)    {        if(top == T)          {              ans += flow;              while(top != S)            {                  edge[rec[top]].w -= flow;                  edge[rec[top]^1].w += flow;                  top = pre[top];              }              flow = inf;          }          int k = now[top];          while(k != -1)          {              int v = edge[k].v;              if(edge[k].w && dis[top] == dis[v]+1)              {                  flow = min(flow, edge[k].w);                  pre[v] = top; rec[v] = k;                  now[top] = k; top = v;                  break;              }              k = edge[k].next;          }          if(k == -1)          {              int mind = up;              if(--gap[dis[top]] == 0) break;            int k = now[top] = head[top];            while(k != -1)              {                  if(edge[k].w && mind>dis[edge[k].v]) mind = dis[edge[k].v];                  k = edge[k].next;              }              ++gap[dis[top] = mind+1];              top = pre[top];          }      }      return ans;  }  void mapping(){int u, v, w;for(int i = 1; i <= m; ++i)      {          scanf("%d %d %d", &u, &v, &w);         add(u, v, w);      }}int main()  {    while(~scanf("%d %d", &m, &n))      {  up = n, S = 1, T = n;        pre_init();          mapping();        printf("%d\n", SAP(S, T));     }      return 0;  }


学习自图论的魅力-网络流

原创粉丝点击