一、什么是最大流问题 

假设现在有一个地下水管道网络,有m根管道,n个管道交叉点,现在自来水厂位于其中一个点,向网络中输水,隔壁老王在另外一个点接水,已知由于管道修建的年代不同,有的管道能承受的水流量较大,有的较小,现在求在自来水厂输入的水不限的情况下,隔壁老王能接到的水的最大值?

为解决该问题,可以将输水网络抽象成一个联通的有向图,每根管道是一条边,交叉点为一个结点,从u流向v的管道能承受的最大流量称为容量,设为cap[u][v],而该管道实际流过的流量设为flow[u][v],自来水厂称为源点s,隔壁老王家称为汇点t,则该问题求的是最终流入汇点的总流量flow的最大值。

 最大流问题的几种经典解法综述

 

 

二、思路分析

关于最大流问题的解法大致分为两类:增广路算法和预流推进算法。增广路算法的特点是代码量小,适用范围广,因此广受欢迎;而预流推进算法代码量比较大,经常达到200+行,但运行效率略高,如果腹黑的出题人要卡掉大多数人的code,那么预流推进则成为唯一的选择。。。。⊙ ⊙ )

咳咳。。。先来看下增广路算法:

为了便于理解,先引入一个引理:最大流最小割定理。

在一个连通图中,如果删掉若干条边,使图不联通,则称这些边为此图的一个割集。在这些割集中流量和最小的一个称为最小割。

最大流最小割定理:一个图的最大流等于最小割。

大开脑洞一下,发现此结论显而易见,故略去证明(其实严格的证明反而不太好写,但是很容易看出结论是对的,是吧)。这便是增广路算法的理论基础。

在图上从st引一条路径,给路径输入流flow,如果此flow使得该路径上某条边容量饱和,则称此路径为一条增广路。增广路算法的基本思路是在图中不断找增广路并累加在flow中,直到找不到增广路为止,此时的flow即是最大流。可以看出,此算法其实就是在构造最小割。

 最大流问题的几种经典解法综述

增广路算法

 

 

而预流推进算法的思路比较奇葩(没找到比较好的图,只能自行脑补一下了。。= =#):

先将s相连的边流至饱和,这种边饱和的结点称为活动点, 将这些活动点加入队列,每次从中取出一个点u,如果存在一个相邻点v是非活动点,则顺着边u->v 推流,直到u变为非活动点。重复此过程,直到队列空,则此时图中的流flow即是最大流。

 

 

三、SAP算法

最短增广路算法(shortest arguement-path algorithm),简称SAP。目前应用最广的算法,代码简短又很好理解,一般情况下效率也比较高。属于增广路算法的一种,特别之处是每次用bfs找的是最短的路径,复杂度为O(n*m^2)

代码如下:

  1 #include<stdio.h>
  2 
  3 #include<string.h>
  4 
  5 #include<queue>
  6 
  7 #include<iostream>
  8 
  9 using namespace std;
 10 
 11  
 12 
 13 const int maxn = 300;
 14 
 15 const int INF = 1000000+10;
 16 
 17  
 18 
 19 int cap[maxn][maxn]; //流量
 20 
 21 int flow[maxn][maxn]; //容量
 22 
 23 int a[maxn]; //a[i]:从起点 s 到 i 的最小容量
 24 
 25 int p[maxn]; //p[i]: 记录点 i 的父亲
 26 
 27  
 28 
 29 int main()
 30 
 31 {
 32 
 33     int n,m;
 34 
 35     while(~scanf("%d%d", &n,&m))
 36 
 37     {
 38 
 39         memset(cap, 0, sizeof(cap)); //初始化容量为 0
 40 
 41         memset(flow, 0, sizeof(flow)); // 初始化流量为 0
 42 
 43  
 44 
 45         int x,y,c;
 46 
 47         for(int i = 1; i <= n; i++)
 48 
 49         {
 50 
 51             scanf("%d%d%d", &x,&y,&c);
 52 
 53             cap[x][y] += c; // 因为可能会出现两个点有多条边的情况,所以需要全部加起来
 54 
 55         }
 56 
 57         int s = 1, t = m; // 第一个点为源点, 第 n 个点为汇点
 58 
 59  
 60 
 61         queue<int> q;
 62 
 63         int f = 0; // 总流量
 64 
 65  
 66 
 67         for( ; ; ) // BFS找增广路
 68 
 69         {
 70 
 71             memset(a,0,sizeof(a)); // a[i]:从起点 s 到 i 的最小残量【每次for()时 a[] 重新清 0 因此同时可做标记数组 vis】
 72 
 73             a[s] = INF; // 起点残量无限大
 74 
 75             q.push(s);  // 起点入队
 76 
 77  
 78 
 79             while(!q.empty()) // 当队列非空
 80 
 81             {
 82 
 83                 int u = q.front();
 84 
 85                 q.pop(); // 取出队首并弹出
 86 
 87                 for(int v = 1; v <= m; v++) if(!a[v] && cap[u][v] > flow[u][v]) //找到新节点 v
 88 
 89                     {
 90 
 91                         p[v] = u;
 92 
 93                         q.push(v); // 记录 v 的父亲,并加入 FIFO 队列
 94 
 95                         a[v] = min(a[u], cap[u][v]-flow[u][v]); // s-v 路径上的最小残量【从而保证了最后,每条路都满足a[t]】
 96 
 97                     }
 98 
 99             }
100 
101  
102 
103             if(a[t] == 0) break; // 找不到, 则当前流已经是最大流, 跳出循环
104 
105  
106 
107             for(int u = t; u != s; u = p[u]) // 从汇点往回走
108 
109             {
110 
111                 flow[p[u]][u] += a[t]; //更新正向流
112 
113                 flow[u][p[u]] -= a[t]; //更新反向流
114 
115             }
116 
117             f += a[t]; // 更新从 s 流出的总流量
118 
119  
120 
121         }
122 
123         printf("%d\n",f);
124 
125     }
126 
127  
128 
129     return 0;
130 
131 }
View Code

相关文章:

  • 2021-07-05
  • 2022-12-23
  • 2021-08-07
  • 2021-10-15
  • 2022-12-23
  • 2021-11-28
  • 2021-11-22
  • 2021-06-02
猜你喜欢
  • 2021-07-05
  • 2022-12-23
  • 2021-12-02
  • 2022-12-23
  • 2022-01-07
相关资源
相似解决方案