ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

网络最大流

2022-08-12 13:33:32  阅读:155  来源: 互联网

标签:cnt 最大 int res ll 路径 网络 流量


最大流是网络中的基本问题,它是基于带权有向图的。
【模板】网络最大流

对于上面的图,可以想象成输水系统。源点为s(出水口),汇点为t(入水口),每条边都有容量,求如何分配水流才能使t点接受到的流量最大。

那么我们很容易得到以下限制

  • 除了源点与汇点,其他所有点流入的流量等于流出的流量。
  • 一条路径的流量应该等于该路径所有边的最小容量。

残存网络
当我们给网络中的边分配(可以给某条边加流量,也可以减流量)流量后,所有剩余容量大于0的边构成的图,这些边的容量为当前剩余容量。(就是说一条边要是满了(流量等于容量)就把他去掉)

反向边
反向边是极其重要的思想,当我们为边\(u\rightarrow v\)分配流量w后,同时为边\(v\rightarrow u\)增加w容量,这种反向边可以帮助我们“反悔”。

比如上面这张网络,边上的数字代表剩余容量。


我们选择一次路径\(s\to a\to b\to t\),并添加对应的反向边,此时整张图的流量为1。


第二次选择路径(或者说寻找路径)\(s\to b\to a\to t\),并添加对应反向边,整张图的流量变为2。
这时候发现已经找不到新的路径,并且整张图的流量达到最大。

这次寻找路径时,我们选择了\(b\to a\)这条边,这表示什么意思呢?
原先b点的1流量由a提供,选择\(b\to a\)这条边后,\(a\to b\)的容量恢复为1,既a不在向b提供流量,此时由s向b提供1流量,a的流量将流往他处。
这就是“反悔”操作。
容易发现按照这种方法(增加反向边)寻找路径,就算最后我们的路径到达不了t,也不会使当前的总容量减少。
而且在具有反向边的图中进行的操作在原图中也都是合法的。

增广路径
增广路径为在残存网络中从原点到汇点的有向路径。就是我们按照上述方法得到的路径。

那么是不是当一个残存网络中不存在增广路径时,整张网络达到最大容量呢?

是的,下面简单写一下证明过程。
不存在增广路径说明不能从源点(s)走到汇点(t)。
那么我们把所有点分成两个集合,集合S表示源点能走到的点,集合T表示剩下的点,显然t不在S中。
那么整张图可能是下面这个样子。

该图中没有画出反向边,10/10表示流量=容量=10,及流量已满,故集合S不能到达集合T(在残存网络中)。
那么有没有这样一种可能,存在一条从T到S的边,且该边有流量。
如下图。

如果这样一条边被分配了流量,那么在残存网络中会存在一条对应的有剩余容量的反向边,故该点在残存网络中为可达点。
所以若存在从T到S的边,那么改变的流量必为0。
以上的到的结论都是在残存网络中不存在增广路径的前提下,并且从图中我们可以很直观的看出此时的流量为30,而且已经无法在增大,
因为连接这两个集合的“关键边”都已经满了。
这同时有引出了应一个概念,最小割

说通俗一点,最小割就是为了不让水从s流向t,怎么破环水沟代价最小。
最大流最小割定理:源点s和汇点t之间的最小割等于s和t之间的最大流。
那么求解最大流问题变成了寻找增广路径,下面给出几种常用算法。

Ford-Fulkerson

#include<bits/stdc++.h>
using namespace std;
#define ll long long 
const int N=210;
const int M=2*(5e3+10);
int cnt=1,head[N];
struct{
    int to,next;
    ll w;
}e[M];
void add(int u,int v,int w){
    e[++cnt].to=v;
    e[cnt].next=head[u];
    e[cnt].w=w;
    head[u]=cnt;
}
bool vis[N];
int n,m,s,t;
ll dfs(int u,ll f){
    if(u==t)return f;//到达汇点直接返回
    vis[u]=1;
    int v;
    ll w;
    for(int i=head[u];i;i=e[i].next){
        v=e[i].to;w=e[i].w;
        ll res=0;
        if(!vis[v]&&w&&(res=dfs(v,min(f,w)))>0){//找到路径修改相关的边
            e[i].w-=res;
            e[i^1].w+=res;
            return res;//找到一条路径后及时返回
        }
    }
    return 0;
}
ll FF(){
    ll ans=0;
    ll res=0;
    do{
        memset(vis,0,sizeof(vis));
        res=dfs(s,1e10);
        ans+=res;
    }while(res!=0);//res==0表示没有增广路径了
    return ans;
}
int main(){
    scanf("%d%d%d%d",&n,&m,&s,&t);
    int u,v,w;
    for(int i=1;i<=m;++i){
        scanf("%d%d%d",&u,&v,&w);
        add(u,v,w);//正向边
        add(v,u,0);//反向边
    }
    printf("%lld",FF());
    return 0;
}

该算法就是不断使用dfs寻找路径,找到一条路径后返回并修改相关的边与反向边。
然而一次dfs只能找到一条路径,而且复杂度及其不靠谱。

Edmonds-Karp

#include<bits/stdc++.h>
using namespace std;
const int N=210;
const int M=5e3+10;
struct{
    int u,to,next,w;
}e[2*M];
int head[N],cnt=1;
void add(int u,int v,int w){
    e[++cnt].to=v;
    e[cnt].u=u;
    e[cnt].next=head[u];
    e[cnt].w=w;
    head[u]=cnt;
}
int n,m,s,t;
int a[N],p[N];
long long ek(){
    long long ans=0;
    while(1){
        queue<int>q;
        memset(a,0,sizeof(a));
        a[s]=INT_MAX;q.push(s);
        while(q.size()){//开始bfs
            int u=q.front();q.pop();
            int v,w;
            for(int i=head[u];i;i=e[i].next){
                v=e[i].to;w=e[i].w;
                if(!a[v]&&w){
                    p[v]=i;//记录路径
                    a[v]=min(a[u],w);
                    q.push(v);
                }
            }
            if(a[t])break;
        }
        if(!a[t])break;//没有增广路径
        for(int i=t;i!=s;i=e[p[i]].u){
            e[p[i]].w-=a[t];
            e[p[i]^1].w+=a[t];
        }
        ans+=a[t];
    }
    return ans;
}
int main(){
    scanf("%d%d%d%d",&n,&m,&s,&t);
    int u,v,w;
    for(int i=1;i<=m;++i){
        scanf("%d%d%d",&u,&v,&w);
        add(u,v,w);
        add(v,u,0);
    }
    printf("%lld",ek());
    return 0;
}

该算法使用bfs寻找路径,时间复杂度为\(O(VE^2)\)

Dinic

该算法的理论复杂度为\(O(V^2E)\),上面的算法遍历一次只能找的一条路径,而该算法遍历一次可以找多条增广路径。
如果一个点分配流量给一条路径后还有剩余流量,那么该点可以继续寻找其他可行路径。
但是如果不加限制的寻找路径很可能会在图里面转圈圈导致超时。
所以先使用bfs分层,在用dfs寻找路径。

#include<bits/stdc++.h>
using namespace std;
#define ll long long 
const int N=210;
const int M=2*(5e3+10);
int dep[N];
int head[N],cnt=1;
struct{
    int v,next,w;
}e[M];
int n,m,s,t;
void add(int u,int v,int w){
    e[++cnt].next=head[u];
    e[cnt].v=v;
    e[cnt].w=w;
    head[u]=cnt;
}
bool bfs(){//先用bfs分层,防止dfs的时候死循环
    memset(dep,0,sizeof(dep));
    queue<int>q;
    q.push(s);
    dep[s]=1;
    while(q.size()){
        int v,w,u=q.front();q.pop();
        for(int i=head[u];i;i=e[i].next){
            v=e[i].v;w=e[i].w;
            if(dep[v]||!w)continue;
            dep[v]=dep[u]+1;
            q.push(v);
        }
    }
    return dep[t];//dep[t]的值可以直接表示是否存在增广路
}
ll dfs(int u,ll in){
    if(u==t)return in;
    ll out=0,v,w;
    for(int i=head[u];i;i=e[i].next){//找到路径后不用返回,继续寻找其他路径
        v=e[i].v;w=e[i].w;
        if(dep[v]!=dep[u]+1||!w)continue;
        int res=dfs(v,min(w*1ll,in));
        in-=res;
        out+=res;
        e[i].w-=res;
        e[i^1].w+=res;
    }
    if(out==0)dep[u]=0;//out=0表示该点的流量不能到大汇点,下次dfs到它时不再继续寻找
    return out;
}
ll din(){
    ll ans=0;
    while(bfs())
    ans+=dfs(s,1e10);
    return ans;
}
int main(){
    scanf("%d%d%d%d",&n,&m,&s,&t);
    int u,v,w;
    for(int i=1;i<=m;++i){
        scanf("%d%d%d",&u,&v,&w);
        add(u,v,w);
        add(v,u,0);
    }
    printf("%lld",din());
    return 0;
}

分层只是为了dfs不陷入死循环,并不会影响答案的正确性,因为一个图若存在增广路,那么也一定能被bfs找到。

标签:cnt,最大,int,res,ll,路径,网络,流量
来源: https://www.cnblogs.com/hetailang/p/16556981.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有