【学员专栏011期】网络流之最大流问题-何佳龙

  • 作者:何佳龙

  • ID:hjl666

  • 学校:长沙市中雅培粹 七年级

  • 获奖:2019年普及组一等奖,提高组二等奖

  • 博客地址:

  • https://www.luogu.com.cn/blog/hjl666/wang-luo-liu-zui-da-li

最大流

摘要

在最大流问题中,我们意在寻找一个带权有向图G中从一个特定的源点s到一个特定的汇点t的最大流。

寻找最大流有几种不同的算法,包括Ford Fulkerson算法,Edmonds Karp算法和Dinic算法

最大流的对偶问题是最小割,也就是在寻找图G的最大s-t流的同时找到图G的最小s-t割,也就是为了使图G中s和t不连通所需要移除的权重最小的边的集合。


例题引入:

你所在的村庄新开通了地下流水管道,自来水厂源源不断的提供水,村民们用水直接或间接用水,而村庄用完的废水统一回收于另一点(设排来的水全部回收)。当然每个管道有一定的容量,废水站求出最多可以汇聚多少水?

想一想:

提取关键信息:

首先水是源源不断的,经过了有限制流量的管道,求最多能流入多少水

所以:

1.实流量<=限制流量

2.对于每个除源汇之外的点:总流入量=总流出量


思考做法

首先我们想可以先dfs一遍,找一条路

再在剩下的网络中找其他的路径并将其加入答案

但这时会发现,开始选的答案可能会影响后续更优的答案,即局部最优解不是全局最优解,那么我们想到可以让其有回退--即反悔的机会,使最后的解一定是最优解


引入概念:

(大致理解即可)

流量:每条边各自被经过的次数称作其流量,最终收集的总数为整个流的流量

容量:每条边都有一个容量

w(x,y)容量限制

flow(x,y)从x到y的是实流量

增广路径:从s到t找到一条流量非0的路径

残余网络:将非增广路径边正方向建图,增广路径上的边反方向建图

反悔机制

当你用dfs找到一条增广路径时,可能不是正确答案(想想就知道

那么我们可以将增广路上的边建反边--即反悔机制

总结做法

1.在图上找到一条从源点到汇点的路径--增广路

2.在残余网络中接着找,累加至ans

3.同时继续建反边

4.直至无法到达。

这便是:Ford-Fulkerson算法

出大问题:时间!!!

于是就有了Edmond-Karp算法


EK算法 O

1.bfs找增广路径

2.更新残余网络

3.重复1,2步,直至找不到

代码

bool bfs() { memset(vis, 0, sizeof(vis)); queue<int> q; q.push(s); vis[s] = true; increase[s] = inf; //最小剩余容量 while(q.empty() == false) { int x = q.front(); q.pop(); for(int i = head[x]; i; i = nxt[i]) if(edge[i]) //容量不为0 { int y = to[i]; if(vis[y] == true) continue; increase[y] = min(increase[x], edge[i]); pre[y] = i; q.push(y); vis[y] = true; if(y == t) return true; } } return false;}

比较

FF用dfs找路径

EK用bfs找最短路径


Dinic算法 O

EK算法后,时间复杂度似乎还不够优化,那我们能不能使时间更优呢,在EK中,我们已经是有每次去一个最优解,所以在找法上似乎已经没办法了,这时注意到我们每次只找一个,那为什么不一次找多条么??

思想:一次找几条

实现

bfs找各个点距s的层次

dfs找多条路径

演示

首先标记每个点的层次

找到一条路径

接着在残余网络中标记每个点的层次

再次在残余网络中寻找路径

最终无路可选

代码

int dinic(int x, int flow) //x当前结点, flow表示当前的容量限制 {    if(x == t)         return flow; //返回增广流量    int rest = flow, increase; //剩余的流量    for(int i = head[x]; i && rest; i = nxt[i])    {        if(edge[i] && level[to[i]] == level[x] + 1) //流量非0,层次加1的点我才去        {            increase = dinic(to[i], min(rest, edge[i]));//找从to[i]出发到达t能得到的增广流量            if(increase == 0)                 level[to[i]] = 0; // 去掉增广完毕的点, 剪枝             edge[i] -= increase; //正向边i扣除流量            edge[i ^ 1] += increase; //反向边增加流量            rest -= increase;        }    }    return flow - rest; //增广的流量 = 容量上限 - 剩余的流量 }

例题讲解

P2766 最长不下降子序列问题

第一问dp即可

第二问:对于满足条件的,连一条容量为一的边

第三问:对于满足条件的,除x1和xn外连一条容量为一的边,否则连一条容量为无穷大的边

代码

自己看题解吧~


备注

若有哪里不懂得欢迎提出意见~

(0)

相关推荐