(菜鸟都能看懂的)网络最大流最小割,Ford-Fulkerson及Dinic详解
关于网络流:
1.定义
个人理解网络流的意思便是由一条条水管以及一个源点S一个汇点T和一些节点组成的一张图,现在要从S点流水到T点,问怎么流才能让流到T的流量最大。边权表示的是这条水管的最大流量,假设一条水管的边权是4,那么如果往这个水管里流5那么自然就会炸掉。
关于网络流一些文字上的概念,和一张图,这张图的最大流显然是3。
增广路:从s到t的一条简单路径,且每一条边的容量都大于0。
流(flow):每条边对应其流量的集合。
可行流:从s到t的一个流,且每条边的流量不超过其容量限制。
最大流:流量最大的可行流。
那么怎么找最大流呢,不断找增广路,直到找不到为止?
这个算法很明显是错误的,用刚刚那个图就可以说明。盲目地找一条增广路,显然是不够“聪明”的。 但我们似乎没办法让它变得更“聪明”。那我们不妨给他一个”反悔“的就会。
假设有有一条路径从u->v那么我们就建一条v->u的有向边,称之为反向边,他的边权是0。为什么是0?因为刚开始还没水流从u->v流过,所以没有水可以反悔,假若从u->v流过x的水,那么v->u的边权就是x,因为他可以反悔x的水。
这张图就说明了他的反向边地优越性两条流向撞还是两条流!在做的各位可能跟我刚开始一样起了疑问,两条流撞在一起万一水管小不会爆掉吗?嗯,其实他指的是流量,一道水流过这条水管后,他还会流到别的水管,难道流过一次水管就不能流了吗?我们考虑的是流量,这就要追究的问题,他问的不是一堆水从S点流向其他管子里,问流到T的最多是多少,而是流量最多是多少。
那么如果以这个想法,之前那么想法显然就是对的了,这种算法叫做Ford-Fulkerson算法,但是如果单纯的这么做显然时间复杂度不允许,算一算他的复杂度是O(maxflow),他的时间复杂度由边权决定。但是还是得学。我们把它简称为FF算法。
FF算法的流程:
1.建出带反向边的网络。
2.不断Dfs找增广路,然后沿着增广路流。
3.直到找不到增广路。
完美
代码实现:
1.首先初始化:
const int oo=0x7fffffff; //可爱的无限大
struct e{ //边
int cap,to,from; //cap是边权,to是到达的点,from是从哪里来
int next,rev; //rev是反向边的下标。
}edge[200005];
int head[200005];
bool vis[200005]; //记录每个点访问了没有。
int n,m,s,f; //n是点个数,m是边个数,s是起点,f是终点
int cur,ans; //ans自然就是答案
2.用链式前向星存边:
void Scanf(int x,int y,int z){ //x是来自何方,y是到何方,z是权值
cur++; //大写的Scanf格外诱人
edge[cur].cap=z;
edge[cur].to=y;
edge[cur].from=x;
edge[cur].next=head[x];
head[x]=cur;
edge[cur].rev=cur+1; //指向他的反向边
cur++;
edge[cur].cap=0; //反向边
edge[cur].to=x;
edge[cur].from=y;
edge[cur].next=head[y];
head[y]=cur;
edge[cur].rev=cur-1; //指向他的反向边
}
3.接着是核心部分:
int Search_flow(int node,int flow){//找增广路用的DFS
if(node==f||flow==0)return flow;//如果水都流没了或者当前搜索到的点已经是终点那么自然就返回流量。
vis[node]=true; //标记该点走过
int tre=0; //记录流量
for(int p=head[node];p;p=edge[p].next){
if(!vis[edge[p].to]&&edge[p].cap){//如果没访问过而且边权大于0
int fuck=Search_flow(edge[p].to,min(flow,edge[p].cap));//fuck不要介意,就接着找增广路。
flow-=fuck; //流量减掉已经流掉的
tre+=fuck; //加上流过的水量
edge[p].cap-=fuck; //减掉流过的
edge[edge[p].rev].cap+=fuck;//给他反悔的机会反向边加上流掉的量
}
}
vis[node]=false;//将此点还原
return tre; //返回流量
}
int Search_ans(){
int flow=0;
while(1){
for(int i=1;i<=n;i++)vis[i]=false; //清0不重要
int new_flow=Search_flow(s,oo); //一直找增广路
if(new_flow>0)flow+=new_flow; //要是找得到那么最大流加上这个数
else break; //否则说明没路可走退出
}
return flow;
}
完整代码:
#include<bits/stdc++.h>
using namespace std;
const int oo=0x7fffffff;
struct e{
int cap,to,from;
int next,rev;
}edge[200005];
int head[200005];
bool vis[200005];
int n,m,s,f;
int cur,ans;
void Scanf(int x,int y,int z){
cur++;
edge[cur].cap=z;
edge[cur].to=y;
edge[cur].from=x;
edge[cur].next=head[x];
head[x]=cur;
edge[cur].rev=cur+1;
cur++;
edge[cur].cap=0;
edge[cur].to=x;
edge[cur].from=y;
edge[cur].next=head[y];
head[y]=cur;
edge[cur].rev=cur-1;
}
int Search_flow(int node,int flow){
if(node==f||flow==0)return flow;
vis[node]=true;
int tre=0;
for(int p=head[node];p;p=edge[p].next){
if(!vis[edge[p].to]&&edge[p].cap){
int fuck=Search_flow(edge[p].to,min(flow,edge[p].cap));
flow-=fuck;
tre+=fuck;
edge[p].cap-=fuck;
edge[edge[p].rev].cap+=fuck;
}
}
vis[node]=false;
return tre;
}
int Search_ans(){
int flow=0;
while(1){
for(int i=1;i<=n;i++)vis[i]=false;
int new_flow=Search_flow(s,oo);
if(new_flow>0)flow+=new_flow;
else break;
}
return flow;
}
int main(){
cin>>n>>m>>s>>f;
for(int i=1;i<=m;i++){
int ui,vi,si;
cin>>ui>>vi>>si;
Scanf(ui,vi,si);
}
int ans=Search_ans();
cout<<ans<<endl;
return 0;
}
然后呢我们来讲讲更优的算法名字叫做Dinic算法。开一个数组level表示从源点到达当前点的最小步数。
Dinic的核心思想:每次Dfs都只找经过边数最少的增广路。 具体实现方法是:在Dfs前,先用容量>0的边进行一次Bfs,定出每个点的level。在之后的Dfs中,只允许使用level[v]==level[u]+1的边u->v。 这样Dfs时,我们相当于在一个DAG上增广,所有增广路长度都是level[t]的。增广完所有长度为level[t]的增广路后停止。这样就会节省时间。
Tip:Dinic的时间复杂度是O(nm²)。某种不成文的规定,凡是正解是最大流的题目,不允许卡Dinic.QWQ
Dinic算法流程:
用有容量的边进行Bfs,对网络中的点分层。
只使用符合分层情况的边,进行一次Dfs(多路增广),增广完所有长度为level[t]的增广路。
重复上面两个过程,直到s通过Bfs无法到达t。
Dinic的当前弧优化:记录上次Dfs到这个点时,扫到哪一条边。下次再到这个点时,直接从该边开始,避免对一条边进行无用的检查。
代码核心:
int BFS(){ //BFS找到level
for(int i=1;i<=n;i++){ //初始化
level[i]=0;
nhead[i]=head[i];
}
queue<int> q; //BFS的队列首先从起点s开始找
q.push(s);
level[s]=1; //自己到自己设置为1
while(!q.empty()){
int x=q.front();
q.pop();
for(int p=head[x];p;p=e[p].next)
if(e[p].cap&&!level[e[p].to]){ //如果当前边的边权>0并且没有标记过就标记
q.push(e[p].to);
level[e[p].to]=level[x]+1;
}
}
return level[t]; //返回终点的值如果
}
int DFS(int root,int flow){
if(root==t||!flow)return flow; //与FF中同理同理
int tre=0;
for(int p=nhead[root];p;p=e[p].next) //当前弧优化
if(e[p].cap&&level[root]+1==level[e[p].to]){
int new_flow=DFS(e[p].to,min(flow,e[p].cap));
flow-=new_flow; //不解释,与FF同理
tre+=new_flow;
e[p].cap-=new_flow;
e[e[p].rev].cap+=new_flow;
if(!flow)break; //如果已经流完了那么就跳出循环
nhead[root]=p; //当前弧优化
}
return tre;
}
int Dinci(){
int max_flow=0;
while(BFS())max_flow+=DFS(s,oo); //如果返回终点的值是0说明已经无法到达终点了,否则找增广路
return max_flow; //因此返回最大流输出
}
完整代码如下:
#include<bits/stdc++.h>
using namespace std;
int n,m,s,t;
const int oo=0x7fffffff;
struct edge{
int from,to,cap;
int rev,next;
}e[200005];
int head[200005];
int nhead[200005];
int cur;
int level[200005];
void Scanf(int x,int y,int z){
cur++;
e[cur].from=x;
e[cur].to=y;
e[cur].cap=z;
e[cur].next=head[x];
head[x]=cur;
e[cur].rev=cur+1;
cur++;
e[cur].from=y;
e[cur].to=x;
e[cur].cap=0;
e[cur].next=head[y];
head[y]=cur;
e[cur].rev=cur-1;
}
int BFS(){
for(int i=1;i<=n;i++){
level[i]=0;
nhead[i]=head[i];
}
queue<int> q;
q.push(s);
level[s]=1;
while(!q.empty()){
int x=q.front();
q.pop();
for(int p=head[x];p;p=e[p].next)
if(e[p].cap&&!level[e[p].to]){
q.push(e[p].to);
level[e[p].to]=level[x]+1;
}
}
return level[t];
}
int DFS(int root,int flow){
if(root==t||!flow)return flow;
int tre=0;
for(int p=nhead[root];p;p=e[p].next)
if(e[p].cap&&level[root]+1==level[e[p].to]){
int new_flow=DFS(e[p].to,min(flow,e[p].cap));
flow-=new_flow;
tre+=new_flow;
e[p].cap-=new_flow;
e[e[p].rev].cap+=new_flow;
if(!flow)break;
nhead[root]=p;
}
return tre;
}
int Dinci(){
int max_flow=0;
while(BFS())max_flow+=DFS(s,oo);
return max_flow;
}
int main(){
scanf("%d%d%d%d",&n,&m,&s,&t);
for(int i=1;i<=m;i++){
int ui,vi,wi;
scanf("%d%d%d",&ui,&vi,&wi);
Scanf(ui,vi,wi);
}
printf("%d",Dinci());
return 0;
}
还有一个问题,没错就是最小割,你只要记住最大流=最小割,也就是叫你求最小割时你就求最大流就行了,网络流这个东西,最主要的是建图,模板的话每天打一两次就好了,提醒一下建图可以运用:拆点,超级源点,超级汇点等思想。
Tip:超级源点,超级汇点指在题目中没有确切给出源点和汇点,然后通过自己的构思,创造出源点和汇点。
那么简单讲讲最小割,就是说在一张最大流的图中求割掉一些边使得源点到不了汇点(或者说是最大流为0)求出割掉的边的边权和最小。
这就是最小割,最小割也有变形,比如说题目说要割点不割边,像这种情况就可以利用拆点,也就是将一个点拆分成两个点,割掉两点中的线好比割掉了这个点,然后确定这条边的边权,再跑一遍最大流即可。
不管是最大流还是最小割最关键的东西就是建图。
转载的话请加上原文网址,哦
谢谢您的观看,记得点个赞再走哦,有什么写得不好的多多指教。
(菜鸟都能看懂的)网络最大流最小割,Ford-Fulkerson及Dinic详解的更多相关文章
- 小学生都能看懂的FFT!!!
小学生都能看懂的FFT!!! 前言 在创新实践重心偷偷看了一天FFT资料后,我终于看懂了一点.为了给大家提供一份简单易懂的学习资料,同时也方便自己以后复习,我决定动手写这份学习笔记. 食用指南: 本篇 ...
- 机器学习敲门砖:任何人都能看懂的TensorFlow介绍
机器学习敲门砖:任何人都能看懂的TensorFlow介绍 http://www.jiqizhixin.com/article/1440
- 只要听说过电脑的人都能看懂的网上pdf全书获取项目
作者:周奇 最近我要获取<概统>的教材自学防挂科(线代已死),于是我看到 htt链ps:/链/max链.book接118接.com接/html/2018/0407/160495927.sh ...
- 55张图吃透Nacos,妹子都能看懂!
大家好,我是不才陈某~ 这是<Spring Cloud 进阶>第1篇文章,往期文章如下: 五十五张图告诉你微服务的灵魂摆渡者Nacos究竟有多强? openFeign夺命连环9问,这谁受得 ...
- 搭建分布式事务组件 seata 的Server 端和Client 端详解(小白都能看懂)
一,server 端的存储模式为:Server 端 存 储 模 式 (store-mode) 支 持 三 种 : file: ( 默 认 ) 单 机 模 式 , 全 局 事 务 会 话 信 息 内 存 ...
- 网络编程之TCP/IP各层详解
网络编程之TCP/IP各层详解 我们将应用层,表示层,会话层并作应用层,从TCP/IP五层协议的角度来阐述每层的由来与功能,搞清楚了每层的主要协议,就理解了整个物联网通信的原理. 首先,用户感知到的只 ...
- WiFi无线网络参数 802.11a/b/g/n 详解
转载自:WiFi无线网络参数 802.11a/b/g/n 详解 如转载侵犯您的版权,请联系:2378264731@qq.com 802.11a/b/g/n,其实指的是无线网络协议,细分为802.11a ...
- linux网络编程之shutdown() 与 close()函数详解
linux网络编程之shutdown() 与 close()函数详解 参考TCPIP网络编程和UNP: shutdown函数不能关闭套接字,只能关闭输入和输出流,然后发送EOF,假设套接字为A,那么这 ...
- Linux网络配置:Nat和桥接模式详解
Linux网络配置:Nat和桥接模式详解 一.我们首先说一下VMware的几个虚拟设备: Centos虚拟网络编辑器中的虚拟交换机: VMnet0:用于虚拟桥接网络下的虚拟交换机: VMnet1:用于 ...
随机推荐
- 使用python统计《三国演义》小说里人物出现次数前十名,并实现可视化。
一.安装所需要的第三方库 jieba (jieba是优秀的中文分词第三分库) pyecharts (一个优秀的数据可视化库) <三国演义>.txt下载地址(提取码:kist ) 使用pyc ...
- 万字长文带你掌握Java数组与排序,代码实现原理都帮你搞明白!
查找元素索引位置 基本查找 根据数组元素找出该元素第一次在数组中出现的索引 public class TestArray1 { public static void main(String[] arg ...
- 用MindManager做完思维导图后怎么加密
使用MindManager我们可以制作出很多简洁明了的思维导图,然而,不论是分享还是查看这些导图,我们往往需要对导图进行加密操作,尤其是制定公司或企业的下一步发展计划时,加密导图就显得很有必要了. M ...
- Boom 3D带你聆听《我们的乐队》音乐盛宴
说到前段时间大热的音乐类综艺节目,<我们的乐队>肯定值得一提.<我们的乐队>是由谢霆锋.萧敬腾.王俊凯担任导师,以团队的形式组成各种风格的乐队,并通过同场比拼,最终选出获胜的乐 ...
- 精尽 MyBatis 源码分析 - SqlSession 会话与 SQL 执行入口
该系列文档是本人在学习 Mybatis 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释(Mybatis源码分析 GitHub 地址.Mybatis-Spring 源码分析 GitHub ...
- BootstrapBlazor 组件库使用体验---Table篇
原文地址:https://www.cnblogs.com/ysmc/p/13323242.html Blazor 是一个使用 .NET 生成交互式客户端 Web UI 的框架: 使用 C# 代替 Ja ...
- 第一次UML编程作业
博客班级 https://edu.cnblogs.com/campus/fzzcxy/2018SE2/ 作业要求 https://edu.cnblogs.com/campus/fzzcxy/2018S ...
- Integer中的奇妙位运算
Integer中的奇妙位运算 参考资料 https://segmentfault.com/a/1190000015763941 highestOneBit(int i) 函数的作用是获得传入参数的最高 ...
- Apache Flink 如何正确处理实时计算场景中的乱序数据
一.流式计算的未来 在谷歌发表了 GFS.BigTable.Google MapReduce 三篇论文后,大数据技术真正有了第一次飞跃,Hadoop 生态系统逐渐发展起来. Hadoop 在处理大批量 ...
- 第1.2节 Python学习环境的使用
Python的环境安装好以后,可以通过IDLE(Python 3.7 64-bit)进入图形界面使用Python,也可以通过Python 3.7 64-bit进入命令行交互式界面,两者都可以使用,不过 ...