关于网络流:

  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. 给力啊!这篇Spring Bean的依赖注入方式笔记总结真的到位,没见过写的这么细的

    1. Bean的依赖注入概念 依赖注入(Dependency Injection):它是 Spring 框架核心 IOC 的具体实现.在编写程序时,通过控制反转,把对象的创建交给了 Spring,但是 ...

  2. 关于oracle11g 和sqldeverloper的安装配置

    0友情提示:以下下载地址都是我的百度云分享链接安全无毒请放心下载! 电脑配置 win10 jdk版本1.7 下载oracle11G 版本 下载地址:安装很简单 sqldverloper(oracleD ...

  3. Mac磁盘清理工具——CleanMyMac

    许多刚从Windows系统转向Mac系统怀抱的用户,一开始难免不习惯,因为Mac系统没有像Windows一样的C盘.D盘,分盘分区明显.因此这也带来了一些问题,关于Mac的磁盘的清理问题,怎么进行清理 ...

  4. css3系列之transform 详解skew

    skew skewx skewy skewX()  倾斜该元素,里面填的是角度,下面↓ 你会看到,随着元素被倾斜,高度居然不变.聪明的你,一定会知道,高度不变,代表了,Y轴被拉伸了. 跟scale 同 ...

  5. css3系列之属性选择器

    Attribute Selectors(属性选择器) E[attr ~="val"] E[attr |="val"] E[attr ^="val&qu ...

  6. testlink——解决测试度量与报告或图表中中文显示乱码问题

    解决问题之前的图表: 解决方法: (1)下载SimHei.TTF字体(可以在自己电脑的C:/windows/fonts目录下找到,若找不到,可以在网上下载) (2)将SimHei.TTF文件拷贝到te ...

  7. 如何测试一个APP

    1.是否支持各种手机系统 2.是否会因为分辨率而出错 3.不同机型能否安装 4.老旧机型 能否通用 5.广告时长 6.测试能否登陆注册 7.卸载时是否会发生意外 8.安装时会不会误认为带病毒 9.用户 ...

  8. jvm系列(一)运行时数据区

    C++程序员肩负着每一个对象生命周期开始到终结的维护责任.Java程序员则可以借助自动内存管理机制,不需要自己手动去释放内存.由虚拟机进行内存管理,不容易出现内存泄漏和内存溢出的问题,但是一旦出现这些 ...

  9. java并发编程实战《二》java内存模型

    Java解决可见性和有序性问题:Java内存模型 什么是 Java 内存模型? Java 内存模型是个很复杂的规范,可以从不同的视角来解读,站在我们这些程序员的视角,本质上可以理解为, Java 内存 ...

  10. MySQL索引(二):建索引的原则

    在了解了索引的基础知识及B+树索引的原理后(如需复习请点这里),这一节我们了解一下有哪些建索引的原则,来指导我们去建索引. 建索引的原则 1. 联合索引 我们可能听一些数据库方面的专业人士说过:&qu ...