寒假Day11:网络流最大流(EK算法+Dinic算法)+HDU1532-Drainage Ditches(两种算法的实现)
网络流基本概念
容量网络:设G(V,E),是一个有向网络,在V中指定了一个顶点,称为源点(记为Vs),以及另一个顶点,称为汇点(记为Vt);对于每一条弧<u,v>属于E,对应有一个权值c(u,v)>0,称为弧的容量.通常吧这样的有向网络G称为容量网络。 弧的流量:通过容量网络G中每条弧<u,v>,上的实际流量(简称流量),记为f(u,v); 网络流:所有弧上流量的集合f={f(u,v)},称为该容量网络的一个网络流. 可行流:在容量网络G中满足以下条件的网络流f,称为可行流. a.弧流量限制条件: 0<=f(u,v)<=c(u,v); b:平衡条件:即流入一个点的流量要等于流出这个点的流量,(源点和汇点除外). 若网络流上每条弧上的流量都为0,则该网络流称为零流. 伪流:如果一个网络流只满足弧流量限制条件,不满足平衡条件,则这种网络流为伪流,或称为容量可行流.(预流推进算法有用) 最大流:在容量网络中,满足弧流量限制条件,且满足平衡条件并且具有最大流量的可行流,称为网络最大流,简称最大流. 弧的类型: a.饱和弧:即f(u,v)=c(u,v); b.非饱和弧:即f(u,v)<c(u,v); c.零流弧:即f(u,v)=0; d.非零流弧:即f(u,v)>0. 链:在容量网络中,称顶点序列(u1,u2,u3,u4,..,un,v)为一条链要求相邻的两个顶点之间有一条弧. 设P是G中一条从Vs到Vt的链,约定从Vs指向Vt的方向为正方向.在链中并不要求所有的弧的方向都与链的方向相同. a.前向弧:(方向与链的正方向一致的弧),其集合记为P+,b.后向弧:(方向与链的正方向相反的弧),其集合记为P-. 增广路: 设f是一个容量网络G中的一个可行流,P是从Vs到Vt 的一条链,若P满足以下条件: a.P中所有前向弧都是非饱和弧, b.P中所有后向弧都是非零弧. 则称P为关于可行流f 的一条增广路. 沿这增广路改进可行流的操作称为增广. 残留容量:给定容量网络G(V,E),及可行流f,弧<u,v>上的残留容量记为cl(u,v)=c(u,v)-f(u,v).每条弧上的残留容量表示这条弧上可以增加的流量.因为从顶点u到顶点v的流量减少,等效与从顶点v到顶点u的流量增加,所以每条弧<u,v>上还有一个反方向的残留容量cl(v,u)=-f(u,v). 残留网络:设有容量网络G(V,E)及其上的网络流f,G关于f的残留网络记为G(V',E').其中G'的顶点集V'和G中顶点集G相同,V'=V.对于G中任何一条弧<u,v>,如果f(u,v)<c(u,v),那么在G'中有一条弧<u,v>属于E',其容量为c'(u,v)=c(u,v)-f(u,v),如果f(u,v)>0,则在G'中有一条弧<v,u>属于E',其容量为c'(v,u)=f(u,v).残留网络也称为剩余网络.
最大流核心部分:引入反向边
Edmonds-Karp(EK)算法 思路理清:
(具体实现代码看下面的例题)
相比FF来说,引入了反向边这个概念
利用bfs去找增广路,直到我们找不到为止
找到:更新最大流和残余网络
Dinic算法
(具体实现代码看下面的例题)
- Edmonds-Karp(EK)算法:
每搜索出一条路径就要进行一次bfs,Dinic就是对bfs的次数进行优化。
- Ford-Fulkerson (FF)算法:
O(n × m × m),不适用边比较多的情况。
在此基础上进行了优化产生了Dinic算法。
- 算法实现过程:
1、通过bfs进行分层:引入了分层图这个概念,从一个节点bfs能够同时到达的点为同一层
如果无法通过bfs分层走到终点,等价于无法进行增广
若能进行增广,则通过dfs反复进行增广,把最大流更新到一个变量上去
depth(2)= depth(1)+1
2、在此基础上进行增广,每次增广的就不只是一条链了,二十很多条链
一次dfs:目的可以进行多次增广
因为进行了分层,所以dfs当前节点的下一个节点的层数的比当前多1(一次dfs可以多次增广的一个原因)
求增广路的dfs和求分层图的bfs交替进行,构成了dinic
- Dinic时间复杂度:每跑一遍bfs,一遍dfs,最坏情况下为 O ( n × n × m )。
由于一般的图边数m≥n,所以一个 × m,一个 × n
- Dinic算法和例题讲解:
- https://www.bilibili.com/video/av18800207?p=1
- https://www.bilibili.com/video/av18800207?p=2 讲的是HDU1532-Drainage Ditches这个例题,代码实现是通过递归更新的
- https://www.bilibili.com/video/av38609321/?spm_id_from=333.788.videocard.6 Dinic算法的优化和例题
- 非递归的dinic更高效,我的下面题目的代码是递归的
- 数据结构:视频里实现的dinic是链式前向星,也就是静态邻接表
- 小技巧:利用01 23 45...一个偶数一个奇数来定义一个正向边和它对应的反向边(所以初始化tot必须为-1)
通过序号异或的形式,由正向边可以得到反向边,反向边也可以得到正向边,
HDU1532-Drainage Ditches:EK算法的具体实现(EK模板)
- 题面:
Every time it rains on Farmer John's fields, a pond forms over Bessie's favorite clover patch. This means that the clover is covered by water for awhile and takes quite a long time to regrow. Thus, Farmer John has built a set of drainage ditches so that Bessie's clover patch is never covered in water. Instead, the water is drained to a nearby stream. Being an ace engineer, Farmer John has also installed regulators at the beginning of each ditch, so he can control at what rate water flows into that ditch.
Farmer John knows not only how many gallons of water each ditch can transport per minute but also the exact layout of the ditches, which feed out of the pond and into each other and stream in a potentially complex network.
Given all this information, determine the maximum rate at which water can be transported out of the pond and into the stream. For any given ditch, water flows in only one direction, but there might be a way that water can flow in a circle.
Input
The input includes several cases. For each case, the first line contains two space-separated integers, N (0 <= N <= 200) and M (2 <= M <= 200). N is the number of ditches that Farmer John has dug. M is the number of intersections points for those ditches. Intersection 1 is the pond. Intersection point M is the stream. Each of the following N lines contains three integers, Si, Ei, and Ci. Si and Ei (1 <= Si, Ei <= M) designate the intersections between which this ditch flows. Water will flow through this ditch from Si to Ei. Ci (0 <= Ci <= 10,000,000) is the maximum rate at which water will flow through the ditch.
Output
For each case, output a single integer, the maximum rate at which water may emptied from the pond.
- 样例:
Sample Input 5 4 1 2 40 1 4 20 2 4 20 2 3 30 3 4 10 Sample Output 50
- 题意:求1-N的最大流 模板题
- EK算法的具体实现:
1、解题需要用到以下数组:
int e[N][N];//用邻接矩阵记录该图 int pre[N];//增广路顶点i前面的顶点序号 bool book[N];//记录bfs中各顶点是否被访问过 int maxflow;//最大流流量
2、注意每条边上的流量需要进行累加:
e[u][v]+=w;//相同边上面的值可以进行累加
3、bfs实现增广路的寻找:
1 bool bfs() 2 { 3 memset(book,0,sizeof(book)); 4 memset(pre,0,sizeof(pre)); 5 queue<int>Q; 6 Q.push(s);//首先让s源点先入队列 7 book[s]=1; 8 while(!Q.empty()) 9 { 10 int p=Q.front(); 11 Q.pop(); 12 if(p==t) 13 return 1;//找到一条增广路径 14 for(int i=1; i<=n; i++) 15 { 16 if(!book[i]) 17 { 18 if(e[p][i]>0) 19 { 20 book[i]=1; 21 pre[i]=p; 22 Q.push(i); 23 } 24 } 25 } 26 } 27 return 0; 28 }
4、找到最大流:
1 int solve() 2 { 3 maxflow=0; 4 while(1) 5 { 6 if(bfs()==0)//找通路,即找增广路 7 return maxflow;//这个bfs往后找一定是会有找不到的情况的,所以这里只需要写一个return就可以了 8 int minn=inf; 9 for(int i=t; i!=s; i=pre[i])//回溯 10 minn=min(minn,e[pre[i]][i]); 11 for(int i=t; i!=s; i=pre[i]) 12 { 13 e[pre[i]][i]-=minn;//改变正向边的值 正向边减少,反向边增加 14 e[i][pre[i]]+=minn;//改变反向边的值 15 } 16 maxflow+=minn; 17 } 18 }
- AC代码:
1 #include<string.h> 2 #include<iostream> 3 #include<stdio.h> 4 #include<algorithm> 5 #include<queue> 6 #include<vector> 7 #include<map> 8 #include<cmath> 9 using namespace std; 10 #define inf 0x3f3f3f3f 11 #define inff 0x3f3f3f3f3f3f3f3f 12 const int N=220; 13 #define mod 998244353 14 typedef long long ll; 15 16 int e[N][N],m,n,s,t; 17 int pre[N];//增广路顶点i前面的顶点序号 18 bool book[N];//记录bfs中各顶点是否被访问过 19 int maxflow;//最大流流量 20 21 bool bfs() 22 { 23 memset(book,0,sizeof(book)); 24 memset(pre,0,sizeof(pre)); 25 queue<int>Q; 26 Q.push(s);//首先让s源点先入队列 27 book[s]=1; 28 while(!Q.empty()) 29 { 30 int p=Q.front(); 31 Q.pop(); 32 if(p==t) 33 return 1;//找到一条增广路径 34 for(int i=1; i<=n; i++) 35 { 36 if(!book[i]) 37 { 38 if(e[p][i]>0) 39 { 40 book[i]=1; 41 pre[i]=p; 42 Q.push(i); 43 } 44 } 45 } 46 } 47 return 0; 48 } 49 50 int solve() 51 { 52 maxflow=0; 53 while(1) 54 { 55 if(bfs()==0)//找通路,即找增广路 56 return maxflow;//这个bfs往后找一定是会有找不到的情况的,所以这里只需要写一个return就可以了 57 int minn=inf; 58 for(int i=t; i!=s; i=pre[i])//回溯 59 minn=min(minn,e[pre[i]][i]); 60 for(int i=t; i!=s; i=pre[i]) 61 { 62 e[pre[i]][i]-=minn;//改变正向边的值 63 e[i][pre[i]]+=minn;//改变反向边的值 64 } 65 maxflow+=minn; 66 } 67 } 68 69 int main() 70 { 71 int u,v,w,tt=1; 72 while(~scanf("%d %d",&m,&n)) 73 { 74 memset(e,0,sizeof(e)); 75 s=1,t=n; 76 for(int i=1; i<=m; i++) 77 { 78 scanf("%d %d %d",&u,&v,&w); 79 e[u][v]+=w;//相同边上面的值可以进行累加 80 } 81 printf("%d\n",solve()); 82 } 83 return 0; 84 }
HDU1532-Drainage Ditches:Dinic算法的具体实现(Dinic模板)
(题面、数据范围等见上面的EK中,以下只解释代码。)
- Dinic的具体实现:
1、需要用到的数组
int e[N][N];//该图 int dep[N];//代表当前层数
2、bfs重新建图,进行分层
1 int bfs()//重新建图、进行分层建图 2 { 3 queue<int>Q; 4 memset(dep,-1,sizeof(dep));//这里注意一下是mem还是for清空,小心超时 5 dep[1]=1;//dep[s]=1; 相当于给源点分层为1 6 Q.push(1);//Q.push(s); 7 while(!Q.empty()) 8 { 9 int u=Q.front(); 10 Q.pop(); 11 for(int i=1; i<=n; i++) 12 { 13 //如果可以到达且还没有访问则入队 14 //可以到达的条件是剩余容量大于0 15 //没有访问的条件是当前层数==-1 16 if(e[u][i]>0&&dep[i]==-1) 17 { 18 dep[i]=dep[u]+1; 19 Q.push(i); 20 } 21 } 22 } 23 return dep[n]!=-1; 24 // !=-1 return 1;//可以通过分层走到终点n 25 // ==-1 return 0;//无法通过分层走到终点n 26 }
3、dfs查找路径上的最小容量
1 int dfs(int u,int minflow)//u起点(第一次传进来是1,之后会变)/查找路径上的最小流量 2 { 3 if(u==n) 4 return minflow; 5 int x; 6 for(int v=1;v<=n;v++) 7 { 8 if(e[u][v]>0&&dep[v]==dep[u]+1&&(x=dfs(v,min(minflow,e[u][v])))) 9 // if(e[u][v]>0&&dep[v]==dep[u]+1) 10 11 { 12 // x=dfs(v,min(minflow,e[u][v])); 13 e[u][v]-=x; 14 e[v][u]+=x; 15 return x; 16 } 17 } 18 return 0;//一定要写!!! 19 }
4、Dinic的调用
1 int dinic() 2 { 3 int sum=0; 4 while(bfs())//先进行分层 5 { 6 while(1) 7 { 8 int w=dfs(1,inf); 9 if(w==0) 10 break;//找不到增广路了 11 sum+=w;//找到则进行累加 12 } 13 } 14 return sum; 15 }
- AC代码:
1 #include<string.h> 2 #include<iostream> 3 #include<stdio.h> 4 #include<algorithm> 5 #include<queue> 6 #include<vector> 7 #include<map> 8 #include<cmath> 9 using namespace std; 10 #define inf 0x3f3f3f3f 11 #define inff 0x3f3f3f3f3f3f3f3f 12 const int N=220; 13 #define mod 998244353 14 typedef long long ll; 15 16 int n,m,e[N][N],dep[N]; 17 18 int bfs() 19 { 20 queue<int>Q; 21 memset(dep,-1,sizeof(dep)); 22 dep[1]=1; 23 Q.push(1); 24 while(!Q.empty()) 25 { 26 int u=Q.front(); 27 Q.pop(); 28 for(int i=1; i<=n; i++) 29 { 30 if(e[u][i]>0&&dep[i]==-1) 31 { 32 dep[i]=dep[u]+1; 33 Q.push(i); 34 } 35 } 36 } 37 return dep[n]!=-1; 38 } 39 40 int dfs(int u,int minflow) 41 { 42 if(u==n) 43 return minflow; 44 int x; 45 for(int v=1; v<=n; v++) 46 { 47 if(e[u][v]>0&&dep[v]==dep[u]+1&&(x=dfs(v,min(minflow,e[u][v])))) 48 { 49 e[u][v]-=x; 50 e[v][u]+=x; 51 return x; 52 } 53 } 54 return 0; 55 } 56 57 int dinic() 58 { 59 int sum=0; 60 while(bfs()) 61 { 62 while(1) 63 { 64 int w=dfs(1,inf); 65 if(w==0) 66 break; 67 sum+=w; 68 } 69 } 70 return sum; 71 72 } 73 int main() 74 { 75 while(~scanf("%d %d",&m,&n)) 76 { 77 memset(e,0,sizeof(e)); 78 for(int i=1; i<=m; i++) 79 { 80 int u,v,w; 81 scanf("%d %d %d",&u,&v,&w); 82 e[u][v]+=w; 83 } 84 int ans=dinic(); 85 printf("%d\n",ans); 86 } 87 return 0; 88 }
这一篇是更新过的Dinic模板(建议写题用这个模板)、上面解释作为理解
https://www.cnblogs.com/OFSHK/p/12247272.html
知识点:
- double提交 输出%lf交C++,交G++输出只能是%f
待解决:
1、没看懂这个结构体的定义。。。???
2、不知道为什么前面的不能写成后面的这种形式???
正确:
if(e[u][v]>0&&dep[v]==dep[u]+1&&(x=dfs(v,min(minflow,e[u][v])))) { e[u][v]-=x; e[v][u]+=x; return x; } //正确
错误:
if(e[u][v]>0&&dep[v]==dep[u]+1) { x=dfs(v,min(minflow,e[u][v])); e[u][v]-=x; e[v][u]+=x; return x; }//错误