网络流 (讲解 + 模板) ----- 详细

网络流

感谢博主 : https://www.cnblogs.com/rmy020718/p/9546071.html

感谢博主 : https://www.cnblogs.com/hyserendipity/p/8560525.html

感谢博主 : https://blog.csdn.net/weixin_43907802/article/details/84705855

感谢博主 : https://www.cnblogs.com/passione-123456/p/12234799.html

感谢博主 :https://www.cnblogs.com/wyxdrqc/p/10634375.html

网络流 :
是指在一个每条边都有容量有向图分配流,使一条边的流量不会超过它的容量。通常在运筹学中,有向图称为网络。顶点称为节点而边称为。一道流必须匹配一个结点的进出的流量相同的限制,除非这是一个源点──有较多向外的流,或是一个汇点──有较多向内的流。一个网络可以用来模拟道路系统的交通量、管中的液体、电路中的电流或类似一些东西在一个结点的网络中游动的任何事物。

算法案例引入 :
然后自来水厂和你家之间修了很多条水管子接在一起 水管子规格不一 有的容量大 有的容量小
然后问自来水厂开闸放水 你家收到水的最大流量是多少

明确概念 :
容量:每条边都有一个容量(水管的最大水流容量)
源点:出发点(水厂)。
汇点:结束点(废水站)。
流:一个合法解称作一个流,也就是一条可以从源点到汇点的一条合法路径。
流量:每条边各自被经过的次数称作其流量,最终收集的总数为整个流的流量。

算法分析 :
容量限制:每条边的流量不超过其容量(水管会爆的)。
流量平衡:对于除源点和汇点以外的点来说,其流入量一定等于流出量。
现在,先简化一下这个图,来解决这个问题。
x/y表示总流量为y,已经流了x.
img
首先想到,随机找路径,然而如果走到如上图所示。
当走完,1->2->3->4就找不到其他路径了,那么答案为1吗?不,答案为2.
首先来手算一下,很容易可以得出这个最大流是走(1,2,4)和(1,3,4)得到的2,即最大流为2
现在改进算法,给流过的路径建反向边,像这样:
img
给程序有反悔的机会。
定义一跳变得残量为:容量 - 已流过的流量。
反向边的流量值=正向流过的总流量,也就是说正向流过多少,反向可以流回多少。
从而又找到1->3->2->4的一条路径。
再次建路径上的反向边,发现没有路径可以到达4点,所以答案为2.

最大流最小割定理 :
最大流最小割定理提供了对于一个网络流,从源点到目标点的最大的流量等于最小割的每一条边的和
这个定理说明,当网络达到最大流时,会有一个割集,这个割集中的所有边都达到饱和状态。
这等价于在网络中再也找不到一个从s到t的增广路径。
因为只要能找到一条增广路径,这条增广路径肯定要经过最小割集中的一条边,否则这个割集就不能称之为割集了。
既然这个割集中所有的边都饱和了,因此也就不会存在这样的增广路径了。
这个定理的意义在于给我们指明了方向:
任何算法,只要最后能达到“再也找不到一条增广路径”,就可以说明这个算法最后达到了最大流。

小结 :
总结一下上面求最大流的步骤:
1.在图上找到一条从源点到汇点的路径(称为‘增广路’)。
2.去增广路上的残量最小值v。(也就是流过的路径中流量最小的那一个)
3.将答案加上v。
4,.将增广路上所有边的残量减去v,反向边的残量加上v。
重复上边4个步骤直到找不到增光路为止

最大流/最小割(Dinic算法)

如果增广一次后发现最短路没有变化,那么可以继续增广,直到源点到汇点的增广路增大,才需要一边bfs。
bfs之后我们去除那些可能在最短路上的边,即dis[终点]=dis[起点]+1的那些边。
显然这些边构成的图中没有环。
只需要延这些边尽可能的增广即可。
bfs处

bool bfs(int s,int t){
    for(int i=0;i<=n+1;i++)deep[i]=inf;
    for(int i=1;i<=n;i++)cur[i]=head[i];
    deep[s]=0;
    q.push(s);
    while(!q.empty()){
        int from=q.front();
        q.pop();
        for(int i=head[from];i!=-1;i=e[i].next){
            int to=e[i].to;
            if(deep[to]==inf&&e[i].w){
                deep[to]=deep[from]+1;
                q.push(to);
            }
        }
    }
    if(deep[t]<inf)return 1;
    else return 0;
}

当图联通时进行dfs,目前节点为u,每次经过与u距离最近的点,并且这条边的残量值要大于0,然后往后进行dfs。
我们在dfs是要加一个变量,作为流量控制(后边的流量不能超过前边流量的最小值)。
dfs中变量flow记录这条管道之后的最大流量。
dfs处 :

int dfs(int from,int t,int limit){
    if(!limit||from==t)return limit;
    int flow=0,f;
    for(int i=cur[from];i!=-1;i=e[i].next){
        cur[from]=i;
        int to=e[i].to;
        if(deep[to]==deep[from]+1&&(f=dfs(to,t,min(limit,e[i].w)))){
            flow+=f;
            limit-=f;
            e[i].w-=f;
            e[i^1].w+=f;
            if(!limit)break;
        }
    }
    return flow;
}

重复上边如果图联通(有最短路径),就一直进行增广。
while(bfs())ans+=dfs(S,inf);

1.Dinic O(n2 * m) n为点数 m为边数 代码里加入了当前弧优化

完整代码 :

#include<bits/stdc++.h>
using namespace std;
const int inf=0x3f3f3f3f;
const int N=3020000;
int s,t,n,m,x,y,z,maxflow,deep[N];
struct Edge{
    int next,to,w;
}e[N];
int cnt=-1,head[N],cur[N];
queue<int >q;
void add(int from,int to,int w){
    e[++cnt].next=head[from];
    e[cnt].to=to;
    e[cnt].w=w;
    head[from]=cnt;
}
bool bfs(int s,int t){
    for(int i=0;i<=n+1;i++)deep[i]=inf;
    for(int i=1;i<=n;i++)cur[i]=head[i];
    deep[s]=0;
    q.push(s);
    while(!q.empty()){
        int from=q.front();
        q.pop();
        for(int i=head[from];i!=-1;i=e[i].next){
            int to=e[i].to;
            if(deep[to]==inf&&e[i].w){
                deep[to]=deep[from]+1;
                q.push(to);
            }
        }
    }
    if(deep[t]<inf)return 1;
    else return 0;
}
int dfs(int from,int t,int limit){
    if(!limit||from==t)return limit;
    int flow=0,f;
    for(int i=cur[from];i!=-1;i=e[i].next){
        cur[from]=i;
        int to=e[i].to;
        if(deep[to]==deep[from]+1&&(f=dfs(to,t,min(limit,e[i].w)))){
            flow+=f;
            limit-=f;
            e[i].w-=f;
            e[i^1].w+=f;
            if(!limit)break;
        }
    }
    return flow;
}
void dinic(int s,int t){
    while(bfs(s,t)){
        maxflow+=dfs(s,t,inf);
    }
}
int main(){
    memset(head,-1,sizeof head);
    scanf("%d%d%d%d",&n,&m,&s,&t);
    for(int i=1;i<=m;i++){
        scanf("%d%d%d",&x,&y,&z);
        add(x,y,z);
        add(y,x,0);
    }
    dinic(s,t);
    printf("%d",maxflow);
    return 0;
}

Dinic

最大流/最小割(ISAP算法)

ISAP 理论时间复杂度与Dinic相同 但二分图时ISAP更有优势
(另外由于ISAP预处理出来了大致的最大流路径 故当网络流中途带修时ISAP需要重新做bfs 因此优先考虑其他网络流算法)

#include<bits/stdc++.h>
using namespace std;
const int inf=0x3f3f3f3f;
const int N=3020000;
int s,t,n,m,x,y,z,maxflow,deep[N],lay[N];
struct Edge{
    int next,to,w;
}e[N];
int cnt=-1,head[N],cur[N];
queue<int >q;
void add(int from,int to,int w){
    e[++cnt].next=head[from];
    e[cnt].to=to;
    e[cnt].w=w;
    head[from]=cnt;
}
void bfs(int s,int t){
    for(int i=0;i<=n+1;i++){
        deep[i]=inf;
        lay[i]=0;
    } 
    deep[t]=0;
    lay[0]=1;
    q.push(t);
    while(!q.empty()){
        int from=q.front();
        q.pop();
        for(int i=head[from];i!=-1;i=e[i].next){
            int to=e[i].to;
            if(deep[to]==inf){
                deep[to]=deep[from]+1;
                lay[deep[to]]++;
                q.push(to);
            }
        }
    }
    return;
}
int dfs(int from,int t,int limit){
    if(from==t){
        maxflow+=limit;
        return limit;
    }
    int flow=0;
    for(int i=cur[from];i!=-1;i=e[i].next){
        int to=e[i].to;
        cur[from]=i;
        if(deep[to]+1==deep[from]&&e[i].w){
            int f=dfs(to,t,min(e[i].w,limit));
            flow+=f;
            limit-=f;
            e[i].w-=f;
            e[i^1].w+=f;
            if(!limit)return flow;
        }
    }
    lay[deep[from]]--;
    if(lay[deep[from]]==0)deep[s]=n+1;
    deep[from]++;
    lay[deep[from]]++;
    return flow;
}
void ISAP(int s,int t){
    bfs(s,t);
    while(deep[s]<n){
        for(int i=1;i<=n;i++)cur[i]=head[i];
        dfs(s,t,inf);
    }
}
int main(){
    memset(head,-1,sizeof head);
    scanf("%d%d%d%d",&n,&m,&s,&t);
    for(int i=1;i<=m;i++){
        scanf("%d%d%d",&x,&y,&z);
        add(x,y,z);
        add(y,x,0);
    }
    ISAP(s,t);
    printf("%d",maxflow);
    return 0;
}
ISAP

最小费用最大流

问题 :
给出一个网络图,以及其源点和汇点,每条边已知其最大流量和单位流量费用,求出其网络最大流和在最大流情况下的最小费用。
实现 :
那么他既然要求最小花费,我们不妨把这个最小花费看成边的权值,构建一个图用最短路算法来找到源点到各个点的最短距离
找到这个数据之后,我们就可以沿着最短路来进行增广,即在最短路中求到一条可行路然后修改其残量,我们可以保证其为最大流中的一部分的最小花费
不断的进行增广直到我们找到了全部值,然后得解,这就是将dinic和spfa结合起来的求解最小费用最大流问题的方法

#include<bits/stdc++.h>
using namespace std;
const int inf=0x3f3f3f3f;
const int N=120000;
int s,t,n,m,x,y,z,d,maxflow,mincost,limit[N],bac[N],pre[N],dis[N],v[N];
struct Edge{
    int next,to,w,dis;
}e[N];
int cnt=-1,head[N];
queue<int >q;
void add(int from,int to,int w,int dis){
    e[++cnt].next=head[from];
    e[cnt].to=to;
    e[cnt].w=w;
    e[cnt].dis=dis;
    head[from]=cnt;
}
int spfa(int s,int t){
    for(int i=1;i<=n;i++){
        dis[i]=inf;
        limit[i]=inf;
        v[i]=0;
    }
    pre[t]=-1;
    q.push(s);
    dis[s]=0;
    v[s]=1;
    while(!q.empty()){
        int from=q.front();
        q.pop();
        v[from]=0; 
        for(int i=head[from];i!=-1;i=e[i].next){
            int to=e[i].to;
            if(dis[to]>dis[from]+e[i].dis&&e[i].w>0){
                dis[to]=dis[from]+e[i].dis;
                pre[to]=from;
                bac[to]=i;
                limit[to]=min(limit[from],e[i].w);
                if(!v[to]){
                    v[to]=1;
                    q.push(to);
                }
            }
        }
    }
    if(pre[t]!=-1)return limit[t];
    else return 0;
}
void MCMF(int s,int t){
    int flow=0; 
    while(flow=spfa(s,t)){
        int k=t;
        while(k!=s){
            e[bac[k]].w-=flow;
            e[bac[k]^1].w+=flow;
            k=pre[k];
        }
        mincost+=flow*dis[t];
        maxflow+=flow;
    }
}
int main(){
    memset(head,-1,sizeof head);
    scanf("%d%d%d%d",&n,&m,&s,&t);
    for(int i=1;i<=m;i++){
        scanf("%d%d%d%d",&x,&y,&z,&d);
        add(x,y,z,d);
        add(y,x,0,-d);
    }
    MCMF(s,t);
    printf("%d %d",maxflow,mincost);
    return 0;
}
//转博客
//https://www.cnblogs.com/passione-123456/p/12234799.html

上下界网络流

1.无源汇上下界可行流:

#include<bits/stdc++.h>
using namespace std;
const int inf=0x3f3f3f3f;
const int N=3020000;
int s,t,n,m,x,y,z,maxflow,deep[N],lay[N],low[N],high[N],out[N],sum;
struct Edge{
    int next,to,w;
}e[N];
int cnt=-1,head[N],cur[N];
queue<int >q;
void add(int from,int to,int w){
    e[++cnt].next=head[from];
    e[cnt].to=to;
    e[cnt].w=w;
    head[from]=cnt;
}
void bfs(int s,int t){
    for(int i=0;i<=n+1;i++){
        deep[i]=inf;
        lay[i]=0;
    } 
    deep[t]=0;
    lay[0]=1;
    q.push(t);
    while(!q.empty()){
        int from=q.front();
        q.pop();
        for(int i=head[from];i!=-1;i=e[i].next){
            int to=e[i].to;
            if(deep[to]==inf){
                deep[to]=deep[from]+1;
                lay[deep[to]]++;
                q.push(to);
            }
        }
    }
    return;
}
int dfs(int from,int t,int limit){
    if(from==t){
        maxflow+=limit;
        return limit;
    }
    int flow=0;
    for(int i=cur[from];i!=-1;i=e[i].next){
        int to=e[i].to;
        cur[from]=i;
        if(deep[to]+1==deep[from]&&e[i].w){
            int f=dfs(to,t,min(e[i].w,limit));
            flow+=f;
            limit-=f;
            e[i].w-=f;
            e[i^1].w+=f;
            if(!limit)return flow;
        }
    }
    lay[deep[from]]--;
    if(lay[deep[from]]==0)deep[s]=n+1;
    deep[from]++;
    lay[deep[from]]++;
    return flow;
}
void ISAP(int s,int t){
    bfs(s,t);
    while(deep[s]<n){
        for(int i=1;i<=n;i++)cur[i]=head[i];
        dfs(s,t,inf);
    }
}
int main(){
    memset(head,-1,sizeof head);
    scanf("%d%d",&n,&m);
    s=n+1,t=n+2;
    for(int i=0;i<m;i++){//×¢Òâ m´Ó0¿ªÊ¼ 
        scanf("%d%d%d%d",&x,&y,&low[i],&high[i]);
        add(x,y,high[i]-low[i]);
        add(y,x,0);
        out[x]-=low[i];
        out[y]+=low[i];
    }
    for(int i=1;i<=n;i++){
        if(out[i]>0){
            sum+=out[i];
            add(s,i,out[i]);
            add(i,s,0);
        }else if(out[i]<0){
            add(i,t,-out[i]);
            add(t,i,0);
        }
    }
    n=n+2;
    ISAP(s,t);
    if(maxflow!=sum){
        printf("NO\n");
    }else{
        printf("YES\n");
        for(int i=0;i<m;i++){
            printf("%d\n",e[i*2|1].w+low[i]);
        }
    }
    return 0;
}

ISAP
//转博客
//https://www.cnblogs.com/passione-123456/p/12234799.html

2.有源汇上下界最大/最小/可行流:

#include<bits/stdc++.h>
using namespace std;
const int inf=0x3f3f3f3f;
const int N=9000300;
int s,t,S,T,n,m,x,y,z,maxflow,deep[N],lay[N],low[N],high[N],out[N],sum;
struct Edge{
    int next,to,w;
}e[N],E[N];
int cnt=-1,num,tot,head[N],cur[N],Head[N];
int ans;
queue<int >q;
void add(int from,int to,int w){
    e[++cnt].next=head[from];
    e[cnt].to=to;
    e[cnt].w=w;
    head[from]=cnt;
}
bool bfs(int s,int t){
    for(int i=0;i<=n+1;i++)deep[i]=inf;
    for(int i=1;i<=n;i++)cur[i]=head[i];
    deep[s]=0;
    q.push(s);
    while(!q.empty()){
        int from=q.front();
        q.pop();
        for(int i=head[from];i!=-1;i=e[i].next){
            int to=e[i].to;
            if(deep[to]==inf&&e[i].w){
                deep[to]=deep[from]+1;
                q.push(to);
            }
        }
    }
    if(deep[t]<inf)return 1;
    else return 0;
}
int dfs(int from,int t,int limit){
    if(!limit||from==t)return limit;
    int flow=0,f;
    for(int i=cur[from];i!=-1;i=e[i].next){
        cur[from]=i;
        int to=e[i].to;
        if(deep[to]==deep[from]+1&&(f=dfs(to,t,min(limit,e[i].w)))){
            flow+=f;
            limit-=f;
            e[i].w-=f;
            e[i^1].w+=f;
            if(!limit)break;
        }
    }
    return flow;
}
void dinic(int s,int t){
    while(bfs(s,t)){
        maxflow+=dfs(s,t,inf);
    }
}
int main(){
    memset(head,-1,sizeof head);
    scanf("%d%d%d%d",&n,&m,&S,&T);
    s=n+1,t=n+2;
    for(int i=0;i<m;i++){
        scanf("%d%d%d%d",&x,&y,&low[i],&high[i]);
        add(x,y,high[i]-low[i]);
        add(y,x,0);
        out[x]-=low[i];
        out[y]+=low[i];
    }
    for(int i=1;i<=n;i++){
        if(out[i]>0){
            sum+=out[i];
            add(s,i,out[i]);
            add(i,s,0);
        }else if(out[i]<0){
            add(i,t,-out[i]);
            add(t,i,0);
        }
    }
    add(T,S,inf);
    add(S,T,0);
    n=n+2;
    dinic(s,t);
    s=T,t=S;
    if(maxflow!=sum){//ÅжÏÊÇ·ñΪÓÐÔ´»ãÉÏϽç¿ÉÐÐÁ÷ 
        printf("please go home to sleep\n");
    }else{
        ans=e[cnt].w;
        e[cnt].w=0;
        e[cnt^1].w=0;
        s=S,t=T;
        //ÒÔÏÂΪÇó³öÓÐÔ´»ãÉÏϽç×î´óÁ÷     
        maxflow=0;
        dinic(s,t);
        ans+=maxflow;
        printf("%d\n",ans); 
        //ÒÔÏÂΪÇó³öÓÐÔ´»ãÉÏϽç×îСÁ÷ 
        maxflow=0;
        dinic(T,S);
        ans-=maxflow;
        printf("%d\n",ans);
    }
    return 0;
}

Dinic
//转博客
//https://www.cnblogs.com/passione-123456/p/12234799.html
  • 5
    点赞
  • 39
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值