关于网络流:

  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详解的更多相关文章

  1. 小学生都能看懂的FFT!!!

    小学生都能看懂的FFT!!! 前言 在创新实践重心偷偷看了一天FFT资料后,我终于看懂了一点.为了给大家提供一份简单易懂的学习资料,同时也方便自己以后复习,我决定动手写这份学习笔记. 食用指南: 本篇 ...

  2. 机器学习敲门砖:任何人都能看懂的TensorFlow介绍

    机器学习敲门砖:任何人都能看懂的TensorFlow介绍 http://www.jiqizhixin.com/article/1440

  3. 只要听说过电脑的人都能看懂的网上pdf全书获取项目

    作者:周奇 最近我要获取<概统>的教材自学防挂科(线代已死),于是我看到 htt链ps:/链/max链.book接118接.com接/html/2018/0407/160495927.sh ...

  4. 55张图吃透Nacos,妹子都能看懂!

    大家好,我是不才陈某~ 这是<Spring Cloud 进阶>第1篇文章,往期文章如下: 五十五张图告诉你微服务的灵魂摆渡者Nacos究竟有多强? openFeign夺命连环9问,这谁受得 ...

  5. 搭建分布式事务组件 seata 的Server 端和Client 端详解(小白都能看懂)

    一,server 端的存储模式为:Server 端 存 储 模 式 (store-mode) 支 持 三 种 : file: ( 默 认 ) 单 机 模 式 , 全 局 事 务 会 话 信 息 内 存 ...

  6. 网络编程之TCP/IP各层详解

    网络编程之TCP/IP各层详解 我们将应用层,表示层,会话层并作应用层,从TCP/IP五层协议的角度来阐述每层的由来与功能,搞清楚了每层的主要协议,就理解了整个物联网通信的原理. 首先,用户感知到的只 ...

  7. WiFi无线网络参数 802.11a/b/g/n 详解

    转载自:WiFi无线网络参数 802.11a/b/g/n 详解 如转载侵犯您的版权,请联系:2378264731@qq.com 802.11a/b/g/n,其实指的是无线网络协议,细分为802.11a ...

  8. linux网络编程之shutdown() 与 close()函数详解

    linux网络编程之shutdown() 与 close()函数详解 参考TCPIP网络编程和UNP: shutdown函数不能关闭套接字,只能关闭输入和输出流,然后发送EOF,假设套接字为A,那么这 ...

  9. Linux网络配置:Nat和桥接模式详解

    Linux网络配置:Nat和桥接模式详解 一.我们首先说一下VMware的几个虚拟设备: Centos虚拟网络编辑器中的虚拟交换机: VMnet0:用于虚拟桥接网络下的虚拟交换机: VMnet1:用于 ...

随机推荐

  1. ABBYY FineReader 14如何查看PDF文档

    使用 ABBYY FineReader,您可以轻松查看和编辑任何类型的 PDF文档,就像是一款功能强大的PDF编辑转换器,不仅如此,它还能够允许您复制其中的文本.图片和表格.本文我们来看看如何从&qu ...

  2. 思维导图MindManager有新手引导功能吗

    无论是对于初次使用Mindmanager思维导图软件的新手来说,还是对于有一定软件使用基础的进阶者来说,Mindmanager思维导图软件的帮助功能都能给予用户很大的指导作用. Mindmanager ...

  3. ntfs和fat32的区别

    ntfs和fat32是两种不同的磁盘文件系统格式,虽然他们有一定的相似点,但还是具有很大的差异.今天,小编就带大家了解一下ntfs和fat32的区别. 图1 :u盘 一.分区容量 fat32能够有效管 ...

  4. ModelViewSet基础操作

    1 安装DjangoRestFramework pip install djangorestframework==3.11.1 2.在settings.py中注册 INSTALLED_APPS = [ ...

  5. 【2014广州市选day1】JZOJ2020年9月12日提高B组T3 消除游戏

    [2014广州市选day1]JZOJ2020年9月12日提高B组T3 消除游戏 题目 Description 相信大家玩过很多网络上的消除类型的游戏,一般来说就是在一个大拼图内找出相同的部分进行最大程 ...

  6. 第11.18节 Python 中re模块的匹配对象

    匹配对象是Python中re模块正则表达式匹配处理的返回结果,用于存放匹配的情况.老猿认为匹配对象更多的应该是与组匹配模式的功能对应的,只是没有使用组匹配模式的正则表达式整体作为组0. 为了说明下面的 ...

  7. PyQt程序执行时报错:AttributeError: 'winTest' object has no attribute 'setCentralWidget'的解决方法

    用QtDesigner设计了一个UI界面,保存在文件Ui_wintest.ui中,界面中使用了MainWindow窗口,窗口名字也叫MainWindow,用PyUIC将其转换成了 Ui_wintest ...

  8. PyQt(Python+Qt)学习随笔:部件的inputMethodHints属性

    inputMethodHints属性只对输入部件有效,输入法使用它来检索有关输入法应如何操作的提示,例如,如果设置了只允许输入数字的标志,则输入法可能会更改其可视组件,以反映只能输入数字.相关取值及含 ...

  9. 第15.10节 PyQt(Python+Qt)入门学习:Qt Designer可视化设计界面组件与QWidget类相关的组件属性详解

    PyQt学习有阵子了,对章节的骨架基本考虑好了,准备本节就写组件的属性的,结果一是日常工作繁忙,经常晚上还要加班,二是Qt的组件属性很多,只能逐一学习.研究和整理,花的时间有点长,不过终于将可视化设计 ...

  10. Linux 挂载,卸载光盘

    首先我们点击虚拟机 点击设置 选择CD 接着我们将设备状态两个勾都勾选,并且ISO映像文件选择我们需要挂载的光盘 点击了确定之后,我们到centos7的命令行下 1,创建挂载目录 mkdir /mnt ...