[关闭]
@Yeasion-Nein 2018-07-30T20:03:58.000000Z 字数 12294 阅读 1318

网络流 ———— 从零开始(的异世界)

网络流

最大流

首先,我们要知道什么是网络流,我们可以类比水流在水管里面的流动,假设在一个类似于有向图中的水管道中,有一个自来水厂点,里面有源源不断的自来水流出,然后在另一个点有一个饥♂渴的人正在等待着喝水,他已经年没有喝过水啦,所以我们要想办法能让他喝到尽可能多的水。我们知道,每一根管子都有各自的粗♂细,也就是容量,从S点流出的流量不能超过这条路径上的任意一条边的容量,不然管子就会爆掉的。

而求这个人能够喝到的水的最大量就是最大流问题。这个点称为源点,这个点称为汇点,这样的图就叫做网络流

比如下面这个图的最大流就是++=

picture1

要注意:一个图的最大流路径并不是只有一个(有例外啦),比如上图在上方就是,中间就是,下方也是,然后才总汇成了最大流

然后是可行流,就是假如所有边上的流量都没有超过容量,那么这一组流称为一组可行流,而很显然,一个网络流的最大流就是所有可行流中流量最大的一种。然后可行流中最简单的就是零流。也就是流量为啦。

然后对于求最大流的算法,这里;列举出了两种:

1:算法 ()

首先,我们从最简单的零流开始,假设我们搜索到了一条路径,从源点可以到达汇点,即A:{->->->...->},然后要注意,这条路上的每一条边满足的条件是流量<容量。并不是<=,因为这样会造成程序死循环。

然后我们可以找到这条路上的每一条边的剩余量的最小值,就是说我这个流的每一条边都有这的容量剩余,那么显而易见,我们将这条路上的所有边的流量都加上仍然是一个可行流。

就这样,我们找到了一个比一开始的流更大的流,也就是之前的流量+,我们称这条路为增广路。我们从开始不断地寻找增广路,每次增广候我们都会得到一个比原来更大的流。这也就是为什么之前的流量是严格小于容量的,如果是=的话,程序就会一直寻找到的最小值为,就会形成死循环。

而当找不到增广路的时候,当前的流就是最大流了。

然后具体的算法实现还是有些东西的。举一个已经用烂了的例子来说明吧。

picture2

在这里,我们假设傻傻的程序先找到了->->->这条路径,我们就会发现程序找出来的最大流为,然后更改{->}、{->}、{->}的

picture3

然而我们能很明显的看出来如果流{->->}、{->->}的话,能够得到更大的流量2,于是我们就知道这个程序是有问题的咯。所以我们反思:为什么会出错呢?

答案就是:你可能很聪明,一眼就看出来怎么走,但是程序是傻的,它只会按照你给的机制跑,但是很无奈你并没有给它一个反悔的机制,于是就凉凉了~

在这里我们采用加反向边的做法,对每一条{}都建一条容量为的反向边{}。然后整个图建完是这样的:

picture4

我们在每一次找到增广路的时候会将每一段的容量减少,同时我们也将这些反向边容量增加。即:

-=; +=;

然后按刚才的方法我们先找到了->->->,然后更改.

然后我们再次寻找增广路,找到->->->,,再次增广之后,我们得到了最大流2。就相当与是把流进来的->又给退了回去,最后结果就是选了->->->->。这就是原由。

而这个网络流版本就是算法。

  1. #define MAXN 100010
  2. #define INF 0x7fffffff
  3. bool visit[MAXN];
  4. int pre[MAXN];
  5. queue<int> q;
  6. void update(int now,int rest){
  7. while(pre[now]){
  8. map[pre[now]][now]-=rest;//正向的-=rest
  9. map[now][pre[now]]+=rest;//负向的+=rest
  10. now=pre[now];
  11. }
  12. }
  13. int find(int S,int T){//寻找增广路流量
  14. memset(visit,0,sizeof(visit));
  15. memset(pre,-1,sizeof(pre));
  16. visit[S]=1; int minn=INF;
  17. q.push(S);
  18. while(!q.empty()){
  19. int now=q.front(); q.pop();
  20. if(now==t) break;
  21. for(int i=1;i<=m;i++){
  22. if(!visit[i]&&MAP[now][i]){
  23. q.push(i);
  24. minn=min(minn,map[now][i]);//最小的rest
  25. pre[i]=now; visit[i]=1;
  26. }
  27. }
  28. }
  29. if(pre[i]==-1) return 0;
  30. return minn;
  31. }
  32. int EK(int S,int T){ //EK算法主体
  33. int new_flow=0;//增广路的流量
  34. int max_flow=0;//最大流
  35. do{
  36. new_flow=find(S,T);
  37. update(T,new_flow);
  38. max_flow+=new_flow;
  39. }while(new_flow);
  40. return max_flow;
  41. }

恩,算法差不多就是这个样子。

算法

首先我们要知道几个重要的基本概念:

也是基于增广路的思想进行运算的,我们用另一种方式重申一遍增广路的基本步骤

1.找到一条从源点到汇点的路径,使得这条路径上任意一条边的残余流量>0,这条路径便称为增广路

2.找到这条路径上最小的,然后将这条路径上的所有边的减去这个,对于我们建的反向边的都加上这个

重复此上的过程,直到再也找不到增广路,此时我们就找到了最大流。

然后在我们的算法中我们引入分层图的概念,具体就是对于每一个点,我们根据从源点开始的序列,给每一个点分配一个深度,然后我们进行不断的寻找增广路,而每一次我们由推出必须保证==+ 下面是的代码流程。

  1. #define MAXN 100010
  2. #define INF 0x7fffffff
  3. int n,m,s,t;//n:点数,m:边数,s:源点,t:汇点
  4. struct node{//前向星不解释
  5. int from;
  6. int to;
  7. int next;
  8. int rest;//每一条边的剩余流量
  9. }edge[MAXN*100];
  10. int head[MAXN],total=-1;
  11. void add(int f,int t,int l){
  12. total++;
  13. edge[total].from=f;
  14. edge[total].to=t;
  15. edge[total].rest=l;
  16. edge[total].next=head[f];
  17. head[f]=total;
  18. }
  19. int deep[MAXN];//深度
  20. queue<int> q;//BFS用队列
  21. bool bfs(){//bfs用来寻找分层图,增广路
  22. while(!q.empty()) q.pop();//清空队列
  23. memset(deep,0,sizeof(deep));//清空深度
  24. deep[s]=1; q.push(s);//处理源点
  25. do{
  26. int now=q.front(); q.pop();//取队首
  27. for(int i=head[now];i!=-1;i=edge[i].next){
  28. if(edge[i].rest&&!deep[edge[i].to]){
  29. //如果这条边还有rest并且这个点还没有被发放deep
  30. deep[edge[i].to]=deep[now]+1;
  31. q.push(edge[i].to);//入队列接着bfs
  32. }
  33. }
  34. }while(!q.empty());
  35. if(deep[t]==0) return 0;
  36. //当没有汇点的深度时,说明不存在分层图,也就没有增广路
  37. else return 1; //有增广路,可以跑Dinic。
  38. }
  39. int dfs(int now,int flow){//now:当前节点,flow:当前流量
  40. if(now==t||!flow) return flow;
  41. int res=0;
  42. for(int i=head[now];i!=-1;i=edge[i].next){
  43. if(deep[edge[i].to]==deep[now]+1&&edge[i].rest){
  44. //如果满足分层图并且rest不为0
  45. int rest=dfs(edge[i].to,min(flow,edge[i].rest));
  46. //接着向下dfs。
  47. if(rest>0){//增广成功
  48. edge[i].rest-=rest;//正向边减去剩余流量
  49. edge[i^1].rest+=rest;//负向边加上剩余流量
  50. res+=rest;
  51. flow-=rest;
  52. }
  53. }
  54. }
  55. return res;//没有增广路的话,就返回0.
  56. }
  57. int Dinic(){
  58. int ans=0; //用来记录最大流量
  59. while(bfs()){
  60. while(int d=dfs(s,INF))
  61. ans+=d;
  62. }
  63. return ans;
  64. }

然后我们可以在当中进行一个当前弧优化就是说:我们的时候不从第一条边开始,用一个数组记录点之前循环到了那一条边,就可以起到优化的效果,但是要注意:每一次在函数中建立完一次分层图都要把置为每一个点的第一条边。

  1. #define MAXN 100010
  2. #define INF 0x7fffffff
  3. int n,m,s,t;//n:点数,m:边数,s:源点,t:汇点
  4. struct node{//前向星不解释
  5. int from;
  6. int to;
  7. int next;
  8. int rest;//每一条边的剩余流量
  9. }edge[MAXN*100];
  10. int head[MAXN],total=-1;
  11. void add(int f,int t,int l){
  12. total++;
  13. edge[total].from=f;
  14. edge[total].to=t;
  15. edge[total].rest=l;
  16. edge[total].next=head[f];
  17. head[f]=total;
  18. }
  19. int cur[MAXN];//这就是那个cur,用来当前弧优化
  20. int deep[MAXN];//深度
  21. queue<int> q;//BFS用队列
  22. bool bfs(){//bfs用来寻找分层图,增广路
  23. while(!q.empty()) q.pop();//清空队列
  24. memset(deep,0,sizeof(deep));//清空深度
  25. deep[s]=1; q.push(s);//处理源点
  26. do{
  27. int now=q.front(); q.pop();//取队首
  28. for(int i=head[now];i!=-1;i=edge[i].next){
  29. if(edge[i].rest&&!deep[edge[i].to]){
  30. //如果这条边还有rest并且这个点还没有被发放deep
  31. deep[edge[i].to]=deep[now]+1;
  32. q.push(edge[i].to);//入队列接着bfs
  33. }
  34. }
  35. }while(!q.empty());
  36. if(deep[t]==0) return 0;
  37. //当没有汇点的深度时,说明不存在分层图,也就没有增广路
  38. else return 1; //有增广路,可以跑Dinic。
  39. }
  40. int dfs(int now,int flow){//now:当前节点,flow:当前流量
  41. if(now==t||!flow) return flow;
  42. int res=0;
  43. for(int& i=cur[now];i!=-1;i=edge[i].next){
  44. //这就是当前弧优化的主要部分,前面的&是为了让cur和i一起变化
  45. if(deep[edge[i].to]==deep[now]+1&&edge[i].rest){
  46. //如果满足分层图并且rest不为0
  47. int rest=dfs(edge[i].to,min(flow,edge[i].rest));
  48. //接着向下dfs。
  49. if(rest>0){//增广成功
  50. edge[i].rest-=rest;//正向边减去剩余流量
  51. edge[i^1].rest+=rest;//负向边加上剩余流量
  52. res+=rest;
  53. flow-=rest;
  54. }
  55. }
  56. }
  57. return res;//没有增广路的话,就返回0.
  58. }
  59. int Dinic(){
  60. int ans=0; //用来记录最大流量
  61. while(bfs()){
  62. for(int i=1;i<=n;i++)
  63. cur[i]=head[i];//不要忘了重置
  64. while(int d=dfs(s,INF))
  65. ans+=d;
  66. }
  67. return ans;
  68. }

最大流模板题

下面放上的是洛谷P3376的网络流模板题,就是为了存个完整代码...

题目描述

如题,给出一个网络图,以及其源点和汇点,求出其网络最大流。

输入输出格式

输入格式:

第一行包含四个正整数N、M、S、T,分别表示点的个数、有向边的个数、源点序号、汇点序号。

接下来M行每行包含三个正整数ui、vi、wi,表示第i条有向边从ui出发,到达vi,边权为wi(即该边最大流量为wi)

输出格式:

一行,包含一个正整数,即为该网络的最大流。

输入样例#1:
4 5 4 3
4 2 30
4 3 20
2 3 20
2 1 30
1 3 40
输出样例#1:
50

样例说明:

pic

题目中存在3条路径:

4-->2-->3,该路线可通过20的流量

4-->3,可通过20的流量

4-->2-->1-->3,可通过10的流量(边4-->2之前已经耗费了20的流量)

故流量总计20+20+10=50。输出50。

通过证明:

picture7

  1. #include<iostream>
  2. #include<cstdio>
  3. #include<queue>
  4. #include<cstring>
  5. #include<algorithm>
  6. #define MAXN 100010
  7. #define INF 0x7fffffff
  8. using namespace std;
  9. int n,m,s,t;//n:点数,m:边数,s:源点,t:汇点
  10. struct node{//前向星不解释
  11. int from;
  12. int to;
  13. int next;
  14. int rest;//每一条边的剩余流量
  15. }edge[MAXN*100];
  16. int head[MAXN],total=-1;
  17. void add(int f,int t,int l){
  18. total++;
  19. edge[total].from=f;
  20. edge[total].to=t;
  21. edge[total].rest=l;
  22. edge[total].next=head[f];
  23. head[f]=total;
  24. }
  25. int cur[MAXN];//这就是那个cur,用来当前弧优化
  26. int deep[MAXN];//深度
  27. queue<int> q;//BFS用队列
  28. bool bfs(){//bfs用来寻找分层图,增广路
  29. while(!q.empty()) q.pop();//清空队列
  30. memset(deep,0,sizeof(deep));//清空深度
  31. deep[s]=1; q.push(s);//处理源点
  32. do{
  33. int now=q.front(); q.pop();//取队首
  34. for(int i=head[now];i!=-1;i=edge[i].next){
  35. if(edge[i].rest&&!deep[edge[i].to]){
  36. //如果这条边还有rest并且这个点还没有被发放deep
  37. deep[edge[i].to]=deep[now]+1;
  38. q.push(edge[i].to);//入队列接着bfs
  39. }
  40. }
  41. }while(!q.empty());
  42. if(deep[t]==0) return 0;
  43. //当没有汇点的深度时,说明不存在分层图,也就没有增广路
  44. else return 1; //有增广路,可以跑Dinic。
  45. }
  46. int dfs(int now,int flow){//now:当前节点,flow:当前流量
  47. if(now==t||!flow) return flow;
  48. int res=0;
  49. for(int& i=cur[now];i!=-1;i=edge[i].next){
  50. //这就是当前弧优化的主要部分,前面的&是为了让cur和i一起变化
  51. if(deep[edge[i].to]==deep[now]+1&&edge[i].rest){
  52. //如果满足分层图并且rest不为0
  53. int rest=dfs(edge[i].to,min(flow,edge[i].rest));
  54. //接着向下dfs。
  55. if(rest>0){//增广成功
  56. edge[i].rest-=rest;//正向边减去剩余流量
  57. edge[i^1].rest+=rest;//负向边加上剩余流量
  58. res+=rest;
  59. flow-=rest;
  60. }
  61. }
  62. }
  63. return res;//没有增广路的话,就返回0.
  64. }
  65. int Dinic(){
  66. int ans=0; //用来记录最大流量
  67. while(bfs()){
  68. for(int i=1;i<=n;i++)
  69. cur[i]=head[i];//不要忘了重置
  70. while(int d=dfs(s,INF))
  71. ans+=d;
  72. }
  73. return ans;
  74. }
  75. int main(){
  76. scanf("%d%d%d%d",&n,&m,&s,&t);
  77. for(int i=1;i<=n;i++) head[i]=-1;
  78. for(int i=1;i<=m;i++){
  79. int x,y,l;
  80. scanf("%d%d%d",&x,&y,&l);
  81. add(x,y,l);
  82. add(y,x,0);
  83. }
  84. printf("%d",Dinic());
  85. return 0;
  86. }

最小费用最大流

最小费用最大流的意思其实也很简单,我们现在已经知道最大流是什么了,而我们知道网络流的可行流有很多,而最大流也是不只有一个,题目在每一条边上设定了两个属性:1.cap流量,2.cost费用,要我们输出所有最大流方案中的最小费用。而最小费用最大流最大费用最大流统称费用流。那么接下来是最小费用最大流的完整的题目描述。

题目描述

如题,给出一个网络图,以及其源点和汇点,每条边已知其最大流量和单位流量费用,求出其网络最大流和在最大流情况下的最小费用。

输入输出格式

输入格式:

第一行包含四个正整数N、M、S、T,分别表示点的个数、有向边的个数、源点序号、汇点序号。

接下来M行每行包含四个正整数ui、vi、wi、fi,表示第i条有向边从ui出发,到达vi,边权为wi(即该边最大流量为wi),单位流量的费用为fi。

输出格式:

 一行,包含两个整数,依次为最大流量和在最大流量情况下的最小费用。#

大家还记得我们在讲解最大流的时候讲过一种叫做的算法吧,下面我们来回顾一下:

若一条从源点SS到汇点TT的路径上各条边的剩余容量都大于00,则称这条路径为一条增广路。显然,可以让一股流沿着增广路从SS流到TT,使网络的流量增大。

Edmonds-Karp 算法的思想就是不断用BFS寻找增广路,直至网络上不存在增广路为止。

在每轮寻找增广路的过程中,Edmonds-Karp 算法只考虑图中所有 的边,用找到任意一条从的路径,同时计算出路径上各边的剩余容量的最小值,则网络的流量就可以增加

需要注意的是,当一条边的流量时,根据斜对称性质,它的反向边流量,此时必有。故-算法在 BFS时除了原图的边集之外,还应该考虑遍历中每条边的反向边。

在具体实现时,可以按照邻接表成对存储的技巧,把网络的每条有向边及其反向边存在邻接表下标相邻的两个位置,可以相互用xor\1xor得到。并且每条边只记录剩余容量即可。当一条边流过大小为的流时,令的剩余流量减少,的剩余流量增大

-算法的时间复杂度为,在实际应用中则远远达不到这个上界,效率较高,一般能够处理 规模的网络。

那么我们接下来要讲解的最小费用最大流也是利用算法来解决的,首先,我们沿着最短路进行增广,而路径长度自然就是路径上的费用,而我们建的反向边的费用是对应正向边的费用的相反数(为了保证过程可逆),而反向边的流量像往常一样依然是0。这里我们可以选择用或者是进行最短路,然后就是求解最短路的实际流量并修改相关数据,以便下一次在残余网络上继续进行增广。

说到这里可能会有些懵逼,不要急,我会先通过更简单易懂的语言来讲解,然后我们还会结合代码慢慢的讲。

其实我们要想:最小费用最大流比单纯的最大流多了什么?就是一个路径上的费用,那我们要解决的就是这个费用问题,那么我们要求得最小的费用要用的最好的方法是什么呢?当然就是最短路啊,我们知道算法有一个函数,我们也知道这种最短路求法也是,那么我们就可以很愉快地将函数转化为最短路的算法啦~。

下面我们结合着代码讲解一下:

首先我们来介绍一下下面要用到的变量的名称:

  1. int n,m,s,t;
  2. //n:点数,m:边数,s:起点,t:终点
  3. int dist[MAXN],flow[MAXN];
  4. //dist[i]:从s到i的最短路径长度(SPFA用)
  5. //flow:流量
  6. bool inque[MAXN];;
  7. //inque[i]表示点i是否在队列里面。
  8. int pre[MAXN],rest[MAXN];
  9. //pre[i]:i的前驱节点
  10. //rest[i]第i条边的剩余流量
  11. int max_flow; int min_spent;
  12. //max_flow:最大流
  13. //min_spent:最小花费
  14. queue<int> q;
  15. //队列

加边没有什么问题。

  1. struct node{
  2. int from;
  3. int to;
  4. int len;
  5. //费用
  6. int cap;
  7. //流量
  8. int next;
  9. }edge[MAXN];
  10. int head[MAXN],total=-1;
  11. //要注意将这里的head[MAXN]和total都置为-1!Yeasion在这上面摔了好几次
  12. void add(int f,int t,int l,int c){
  13. total++;
  14. edge[total].from=f;
  15. edge[total].to=t;
  16. edge[total].len=l;
  17. edge[total].cap=c;
  18. edge[total].next=head[f];
  19. head[f]=total;
  20. }

然后主函数颇为稀松,但是要注意建反向边的时候要将流量置为0,将费用置为原正向边的相反数(^)。

  1. int main(){
  2. scanf("%d%d%d%d",&n,&m,&s,&t);
  3. for(int i=1;i<=n;i++) head[i]=-1;
  4. for(int i=1;i<=m;i++){
  5. int x; int y; int l; int c;
  6. scanf("%d%d%d%d",&x,&y,&c,&l);
  7. add(x,y,l,c); add(y,x,0,-c);
  8. //注意加反向边要注意的两点哦
  9. } Edmonds_Karp(s,t);
  10. printf("%d %d",max_flow,min_spent);
  11. return 0;
  12. }

接下来就是函数了,我们在这个函数里面传入了begin和end的参数,如果大家不想传递的话是可以省略掉的

  1. void Edmonds_Karp(int begin,int end){
  2. while(SPFA(begin,end)){
  3. //寻找增广路
  4. int now=end;
  5. max_flow+=rest[end];
  6. //将最大流加上最小流量
  7. min_spent+=dist[end]*rest[end];
  8. //将最小花费加上从s开始到t的最小路径长*到t的最小流量
  9. while(now!=begin){
  10. edge[last[now]].cap-=rest[end];
  11. //正向边减去最小流量
  12. edge[last[now]^1].cap+=rest[end];
  13. //反向边加上最小流量
  14. now=pre[now];
  15. //设置前趋
  16. }
  17. }
  18. }

最后是SPFA的函数,在这里面我们主要进行的操作有三个:1.找出最短路。当然这个最短路的前提条件是每一条边都有剩余流量edge[i].cap,不然没有任何意义。2.寻找最小流量rest。3.寻找每一个点的前驱。然后返回的条件依然是看有没有找到增广路,就是看t的前驱有没有被更新如果没有,自然就是没有任何一条路能够到达t节点。

  1. bool SPFA(int begin,int end){
  2. while(!q.empty()) q.pop();
  3. memset(dist,127,sizeof(dist));
  4. memset(inque,0,sizeof(inque));
  5. memset(rest,127,sizeof(rest));
  6. //每一次的SPFA不要忘了将所有的变量还原
  7. //但千万别还原pre qwq
  8. q.push(begin); inque[begin]=1;
  9. //入队s点 ,初始化
  10. pre[end]=-1; dist[begin]=0;
  11. //SPFA过程
  12. while(!q.empty()){
  13. int now=q.front();q.pop();
  14. inque[now]=false;
  15. for(int i=head[now];i!=-1;i=edge[i].next)
  16. if(edge[i].cap>0)//这里就是网络流的部分了,首先这条边如果没有了残余流量我们肯定不跑
  17. if(dist[edge[i].to]>dist[now]+edge[i].len){
  18. dist[edge[i].to]=dist[now]+edge[i].len;//SPFA松弛操作
  19. pre[edge[i].to]=now;//更新edge[i].to的前驱为now
  20. rest[edge[i].to]=min(rest[now],edge[i].cap);
  21. //寻找最小的流量
  22. if(!inque[edge[i].to]){//入队,继续SPFA
  23. q.push(edge[i].to);
  24. inque[edge[i].to]=1;
  25. }
  26. }
  27. }
  28. if(pre[end]==-1) return 0;
  29. //如果没有找到t即:t的前驱依然是不存在的-1,则没有增广路,返回0,停止EK
  30. else return 1; //继续进行EK
  31. }

下面是的算法代码:

  1. #include<iostream>
  2. #include<cstdio>
  3. #include<queue>
  4. #include<cstring>
  5. #include<algorithm>
  6. #define MAXN 100010
  7. #define INF 0x7fffffff
  8. #define ll long long
  9. using namespace std;
  10. int n,m,s,t;
  11. struct node{
  12. int from;
  13. int to;
  14. int len;
  15. int cap;
  16. int next;
  17. }edge[MAXN];
  18. int head[MAXN],total=-1;
  19. void add(int f,int t,int l,int c){
  20. total++;
  21. edge[total].from=f;
  22. edge[total].to=t;
  23. edge[total].len=l;
  24. edge[total].cap=c;
  25. edge[total].next=head[f];
  26. head[f]=total;
  27. }
  28. int dist[MAXN],flow[MAXN];
  29. bool inque[MAXN]; int last[MAXN];
  30. int pre[MAXN],rest[MAXN];
  31. int max_flow; int min_spent;
  32. queue<int> q;
  33. bool SPFA(int begin,int end){
  34. while(!q.empty()) q.pop();
  35. memset(dist,127,sizeof(dist));
  36. memset(inque,0,sizeof(inque));
  37. memset(rest,127,sizeof(rest));
  38. q.push(begin); inque[begin]=1;
  39. pre[end]=-1; dist[begin]=0;
  40. while(!q.empty()){
  41. int now=q.front();q.pop();
  42. inque[now]=false;
  43. for(int i=head[now];i!=-1;i=edge[i].next)
  44. if(edge[i].cap>0)
  45. if(dist[edge[i].to]>dist[now]+edge[i].len){
  46. dist[edge[i].to]=dist[now]+edge[i].len;
  47. pre[edge[i].to]=now;
  48. last[edge[i].to]=i;
  49. rest[edge[i].to]=min(rest[now],edge[i].cap);
  50. if(!inque[edge[i].to]){
  51. q.push(edge[i].to);
  52. inque[edge[i].to]=1;
  53. }
  54. }
  55. }
  56. if(pre[end]==-1) return 0;
  57. else return 1;
  58. }
  59. void Edmonds_Karp(int begin,int end){
  60. while(SPFA(begin,end)){
  61. int now=end;
  62. max_flow+=rest[end];
  63. min_spent+=dist[end]*rest[end];
  64. while(now!=begin){
  65. edge[last[now]].cap-=rest[end];
  66. edge[last[now]^1].cap+=rest[end];
  67. now=pre[now];
  68. }
  69. }
  70. }
  71. int main(){
  72. scanf("%d%d%d%d",&n,&m,&s,&t);
  73. for(int i=1;i<=n;i++) head[i]=-1;
  74. for(int i=1;i<=m;i++){
  75. int x; int y; int l; int c;
  76. scanf("%d%d%d%d",&x,&y,&c,&l);
  77. add(x,y,l,c); add(y,x,0,-c);
  78. } Edmonds_Karp(s,t);
  79. printf("%d %d",max_flow,min_spent);
  80. return 0;
  81. }
添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注