首页 > 其他分享 >浅谈网络最大流

浅谈网络最大流

时间:2022-11-24 17:15:15浏览次数:56  
标签:最大 增广 int flow 网络 long 流量 浅谈

网络流看着很麻烦,实际搞清楚后就只剩建图了,主要是要先搞清定义(知晓定义的大佬请自行跳过),然后搞定模板,最后练一下建图


 

 

 

有向边(在网络流中叫弧)

起点为u,终点为v且只能从u到v的边。

 

流量

弧上流过去的量f(u,v)(废话)

 

容量

弧的边权即为容量为c(u,v)

 

源点

入度为0的点s,s的流量一般为∞

 

汇点

出度为0的点t,流出t的流量为0(流进来的跑不了)

 

从一点流向另一点的流量,最大流即为流的最大的流量

 

网络

实际就是一张有向图,每条有向边(弧)的边权是容量,另外还有源点和汇点

 

残差网络

在残差网络中,每边的边权为c(u,v)-f(u,v)————实际就是把剩余的容量在另一张网络上体现出来

 

网络流的性质

容量限制 0<=f(u,v)<=c(u,v)

反对称性 f(v,u) = -f(u,v)——————这点很重要,相当于建了反边,用于调整网络流

流守恒性  流进u点的流量 = 流出u点的流量(s,t除外)

 

平衡条件

流入u点的流量=流出u点的流量

 

可行流

符合网络流性质的流

 

特殊的流

零流   f(u,v)=0;

伪流   只满足平衡条件,却不满足容量限制的流

 

弧的类型

饱和弧  f(u,v)=c(u,v)

非饱和弧  f(u,v)<c(u,v)

零流弧  f(u,v)=0

非零流弧  f(u,v)>0

 

历经千辛万苦,总算把网络流的定义说完了,下面开始网络流的算法。

 

Ford-Fulkerson方法

Ford-Fulkerson,简称FFA,更像是一种方法,而不是算法。(因为后文讲的EK实际上就是FFA用bfs实现)

此方法核心在于找增广路(就是从源点到汇点的可行流),然后进行调整即可,直到找不到增广路。

如图,在开始赏析这丑陋的图前,声明一下,(a,b)代表流量为a,容量为b。

 

第一次寻找增广路径,可能(因为FFA方法只要找增广路就行,找到哪条无所谓)会找到S->T的最大可行流量2,然后我们就要进行调整,就是流上的每条弧流量加上可行流量2(实际上程序中都是直接在残差网络中进行处理,原理一样,下文会讲)

然后继续寻找增广路,可能会找到S->1->T的最大可行流量2,然后按上次操作一样进行调整。

继续找,还能找到S->1->2->T的最大可行流量1,再进行调整

然后就发现找不到增广路了,可得知网络最大流为5。

总结一下,FFA其实就是

1.找增广路,找不到就结束。

2.进行调整。

但是,还没完。请观察如下一幅图。(这里是残差网络,实际上是作者偷懒)

 

于是发现找增广路时,可能找到S->2->1->T这条可行流,最大流量为1,然后找不到新的增广路,结束。

但显然我们能发现最大流量应为2,问题出在哪了呢?观察知找增广路时找的不是最优的,那该怎么办?于是采取建反向弧的思想(上文定义部分有提到),即建f(v,u)=-f(u,v),这样我们走错路后,可以寻找新的增广路进行调整(网络流非常大的一个优势就是可调整),最终找到最优的。

具体代码在这里就不写了,因为后文的EK实际上就是用BFS实现找增广路,我们在EK部分展示代码。(最主要的原因是DFS实现的太废了,时间复杂度太高,为O(mf),f为最大流)

为什么DFS实现的FFA复杂度如此之高呢,再看张丑图

 

最大流应为200,但如果倒霉的话,就会S->1->2->T  ,   S->2->1->T走上200趟。如果容量再大点,就……

问题:没找对路,浪费时间。

方法:找最短的路,可以用BFS实现。

于是,我们学会了Edmonds-Karp(EK)算法,实现详见代码。

#include<bits/stdc++.h>
using namespace std;
int n,m,s,t;
bool v[100010];
struct node{
    int from,to,ca,flow,fid,tid;
}e,match[100010];
vector<node> Map[100010];
int get_other(int x,node e){//寻找这条边的另一个点 
    if(x==e.from) return e.to;
    else return e.from;
}
int get_left(int x,node e){//剩余容量 
    if(x==e.from) return e.ca-e.flow;
    else return e.flow;
}
int add_flow(int x,node &e,int y){//调整流量 
    if(x==e.from) e.flow+=y;//正向边流量增加 
    else return e.flow-=y;//反向边流量减少 
}
void add(int a,int b,int c){
    e.from=a; e.to=b; e.ca=c; e.flow=0;//从a到b容量为c,流量为0 
    e.fid=Map[a].size(); e.tid=Map[b].size();  
    Map[a].push_back(e); Map[b].push_back(e);//链式前向星,这里建了反边 
}
bool bfs(){
    memset(v,0,sizeof(v));
    memset(match,0,sizeof(match));
    queue<int> q;
    q.push(s); v[s]=1;
    while(!q.empty()){
        int x=q.front(); q.pop();
        for(int i=0;i<Map[x].size();i++){
            node e=Map[x][i];
            int b=get_other(x,e);
            if(get_left(x,e)>0 && !v[b]){   //非饱和弧,即还有流量能流过去,并且要到的点没走过 
                v[b]=1;
                match[b]=e;        //到b的边设为e 
                q.push(b);         //b入队 
            }
        }
    }
    return v[t];//看有没有到t 
}
int find(){
    int i=t,mf=0x7ffffff;
    while(i!=s){
        mf=min(mf,get_left(get_other(i,match[i]),match[i]));
        i=get_other(i,match[i]);
    }//找最大可行流 
    i=t;
    while(i!=s){
        node e=match[i];
        add_flow(get_other(i,e),Map[e.from][e.fid],mf);
        add_flow(get_other(i,e),Map[e.to][e.tid],mf);
        i=get_other(i,match[i]);
    }//进行调整 
    return mf;
} 
int main(){
    cin>>n>>m;//n个点,m条边 
    for(int i=1;i<=m;i++){
        int a,b,c;
        cin>>a>>b>>c;
        add(a,b,c);//建边 
    }
    cin>>s>>t; //读入源点和汇点 
    int ans=0;
    while(bfs()) ans+=find();//bfs()寻找增广路,find调整 
    cout<<ans<<endl;
    return 0;
}

EK的时间复杂度为O(nm^2),不过本蒻蒟不会证……

例题

草地排水   https://www.luogu.com.cn/problem/P2740

但是能否进行优化呢,可以看到,EK一次找1条增广路,并进行调整,未免有些浪费。那该如何一次找多条增广路呢?可以用DFS,那怎么在用DFS的同时,如何保证走最短的路呢?分层!

 

Dinic

进行分层后再用DFS寻找增广路并调整,就是此算法的基本思路。

分层:就是先用BFS确定每点的深度d(x),然后搜索时延d(v)=d(u)+1的边去搜,就可以了。具体详见代码(实际上是P3376的代码)。

 注意!!!本代码与EK不同的是本代码用的是残差网络!!!

#include<bits/stdc++.h>
using namespace std;
struct ee{
    long long to,next,dis;
}e[100010];
long long head[100010],d[100010],cur[100010],cnt=-1,ans,n,m,s,t,inf=1e12;
void add(long long x,long long y,long long z){
    e[++cnt].next=head[x];
    e[cnt].to=y;
    e[cnt].dis=z;
    head[x]=cnt;
}//一个链式前向星,没什么好说的 
bool bfs(){
    memset(d,127,sizeof(d));       //初始化,每个点初始深度为极大值 
    d[s]=0;      //源点深度为0 
    queue<int> q;
    q.push(s);
    for(int i=1;i<=n;i++) cur[i]=head[i];      //优化,每次将head存入cur,避免在dfs走重复的弧,详见dfs处 
    while(!q.empty()){
        int x=q.front(); q.pop();
        for(int i=head[x];i!=-1;i=e[i].next){
            if(d[e[i].to]>inf && e[i].dis>0){           //没去过且还有流量能流过去 
                d[e[i].to]=d[x]+1;                      //确定深度,分层 
                q.push(e[i].to);
                if(e[i].to==t) return 1;                //到终点,返回true,表示有从源点到汇点的可行流 
            }
        }
    }
    return 0; //没找到汇点,返回false 
}
long long dfs(int x,long long limit){       //x是当前节点,limit是流到x的流量 
    if(!limit || x==t) return limit;        //当走到t,返回流量,以用来调整 
    long long flow=0,f;                     //flow是这点流出的流量,f是某条边流出的流量 
    for(int i=cur[x];i!=-1;i=e[i].next){           //这里不用head,而用cur,是为了避免走一条别走过的没价值的边走多次,浪费时间 
        cur[x]=i;                                  //通过这种方式,是下回从i开始,而不是head[x] 
        int y=e[i].to;
        if(e[i].dis>0 && d[y]==d[x]+1 && (f=dfs(y,min(limit,e[i].dis)))){             //有流量能流过去且d[y]=d[x]+1,是x的下一层,就继续搜索,limit与这条边的剩余容量取min 
            flow+=f;                //从这点流出的流量 
            limit-=f;               //这个点还剩的流量 
            e[i].dis-=f;            //正向边剩余容量减去流出的,f(u,v)加f,那c(u,v)-f(u,v)就减f 
            e[i^1].dis+=f;          //反向边剩余容量加上流出的,f(v,u)=-f(u,v),残差网络中可调整量加上f 
            if(!limit) break;       //没流量了,退出 
        }
    }
    return flow;    //返回流出的量 
}
int main(){
    memset(head,-1,sizeof(head));
    long long x,y,z;
    cin>>n>>m>>s>>t;      //n个点,m条边 
    for(int i=1;i<=m;i++){
        cin>>x>>y>>z;
        add(x,y,z);     //正向边,z是剩余流量 
        add(y,x,0);     //反向边,因为是残差网络,所以剩余流量为0 
    }
    while(bfs()) ans+=dfs(s,inf);       //不断bfs分层,然后dfs搜索 
    cout<<ans<<endl;
    return 0;
} 

这个是Dinic加上些基础优化的版本。

顺便说一句Dinic的时间复杂度为O(n^2m)(身为蒻蒟的我当然也不会证……)

至此,我们终于把网络流的定义和模板学完了!

至于建模,本蒻蒟向各位推荐题单
网络流从入门到入土 #1https://www.luogu.com.cn/training/12097,里面的题还是非常有利于建模的,这东西就跟dp一样,主要靠感觉以及少量小技巧。

本篇语言较为浅显,可能有所不明,欢迎在评论区留言提醒作者。

标签:最大,增广,int,flow,网络,long,流量,浅谈
From: https://www.cnblogs.com/cdx1221/p/16916043.html

相关文章

  • 某集团公司NTP网络时间同步服务器部署方案
    某集团公司NTP网络时间同步服务器部署方案某集团公司NTP网络时间同步服务器部署方案京准电子科技官微——ahjzsz本项目需配备多台HR-901GB网络时间服务器,各作业部部署一......
  • 浅谈mysql高性能调优(一)
    mysql的问题介绍(一)mysql索引的实现原理和数据结构mysql索引设计的技巧mysql聚簇索引和非聚簇索引的区别mysql索引的中级调优方案mysql分布式集群的设计原则mysql如何实现高......
  • Cent os 7配置网络连通外网
    1、在centos7以前,Linux的redhat系列可以使用setup指令产生的图像化界面手动配置ip,但是自从centos7发布以后,该指令无法配置网卡信息,我们可以手动配置或是使用新的图形化界面......
  • 返回字符串中的最大回文数
    /***@authorpshdhx*@date2022-08-0315:20*@Des给你一个字符串s,找到s中最长的回文子串。*输入:s="babad"*输出:"bab"*解释:"aba"同样是符合题意的......
  • 浅谈web性能优化(一)
    压力测试        在浅谈性能优化之前呢,先介绍一下压力测试。项目在上生产环境之前呢,需要先进行压力测试,模拟并发,看看系统的吞吐量大概是多少,告知运维人员的系统吞吐......
  • zabbix监控网络断网情况--初级版
    zabbix初级监控,通过IMCP方式PING即可添加网络网关IP地址,鉴于zabbix一般报警方式采取发送邮件、钉钉、微信报警需要网络环境支持,于是至少设置zabbix服务器双网通,建议有条件......
  • 【Amadeus原创】Centos使用图形化界面配置网络
    1.查看当前ip地址#ipaddr2.图形化界面配置网卡#nmtui界面提示,左右上下配置,OK即可。......
  • Himi浅谈游戏开发de自学历程!(仅供参考)
    ​​ 李华明Himi ​​​原创,转载务必在明显处注明:很多群友进群之后都会问我如何自学;那么今天就专门写个博文说一下,供各位童鞋交流和学习;大家先来看一段我每天时间安排......
  • 浅谈电能管理系统在火力发电厂中的应用
    陈盼安科瑞电气股份有限公司 上海嘉定 201801 摘要:为管控火力发电厂自用电能,降低企业运行成本,通过电能采集技术、信息技术与节能分析理论的融合,进行火电厂电能精细化管......
  • 浅谈高校宿舍水电管理及解决方案
    陈盼安科瑞电气股份有限公司上海嘉定201801摘  要:大学生在校期间的主要活动范围集中于高校宿舍,高校宿舍水电管理工作是保障高校宿舍管理有序开展的基础。在高校宿舍水......