网络流

我之前有一篇博文;
http://blog.csdn.net/largecub233/article/details/54946567
不好意思啊,这博客不是我自己写的,我也不知到是那个傻逼写的;
我自己写的博客我自己怎么会看不懂,对不对;
那博客太垃圾了我靠,我今天从新写一篇;

网络流的大意就看题目嘛
https://www.luogu.org/problem/show?pid=3376
这里的题面就是一个模型;
在自己的做题里,模型往往是要自己构造的;
网络流偶两个点
源点S
汇点T(我有时写E,就是end)
从S出发连用好多调有向边连阿连连到E
网络流有个名次叫增广路,这个你们自己查;
简单可以理解为一条路径从S到T并且可以通过这一条让最大流增大;
最大流这个你们自己看吗,我不想讲了;
这里写图片描述
上面
15-20-5-4就是增广路,因为通过这条路径可以让S到T的流量增加4;
因为路径上最小的权就是4,流过的流量不可以大于权;
当我们吧这条路增广后,这条路就变成了
11-16-1-0;
这条录对答案的贡献是4;
最大流就是经可能的找到增广路并且更新答案;

那么对于这题,我们有了第一个算法
E-K算法
我们进行多次dfs,每次dfs找到一条增广路并更新答案;
因为一单发现一条增广路,那么这一条路径上的值都要修改
对,这个就是大暴力;
原理可以看我的上一篇垃圾博客的图;
然后我们考虑优化;
我们可不可以像dp一样通过更新子节点推的父节点,一遍OK?
不可以;
因为这不是树,一个点有多个父亲;

这时我们迎来一种dinic算法;
先bfs
bfs时我们把当前图分层,就是对于每一个点维护一个deep;
后dfs
用下一层的节点跟新这一层的点,一次性获取多条增广路,一次性求解;
两者不断反复;
这时我们会发现,每次dfs,会有很多边的权值变成0;
那我们在bfs的时候,反遇到边权为0的点就不再深入;
这样不断bfs dfs,一定会遇到bfs无法更新到T节点,这时已经没有增广路,退出;
这里写图片描述
棕色就是深度,我们第一次dfs完后变成

这里写图片描述
答案就增加5
我们再次bfs
这里写图片描述
再dfs
这里写图片描述
答案增加1;
这时,ST已经不相联通了,结束;

但是问题来了,我们怎么dfs?
我们这么可以保证dfs的路径一定是正确的;
我们不能保证;
但是我们有法宝,反向边;
我们建立反向边,一开始值都是0;
当这条边流量增加x
那么这条边的权值减去x;
其反向边就加x;
反向边参与bfs和dfs,并且对答案产生贡献;
我为什么?;
这里写图片描述
我们dfs
第一次
这里写图片描述
第二次

这里写图片描述
答案只有7,其实最大流有9;

现在我们建立反向边;
左边是反向边的值
第一次
这里写图片描述
二次有点乱
ans=4
这里写图片描述
ans=4+3
这里写图片描述
第三次的时候发现中间那边的反向边值为4,所以从中间的边走
这里写图片描述
最终答案=4+3+2=9;
正确;
一开始中间的路向下走4的流量;
后来又通过反向边上去2 的流量;
等同于一开始只下来2的流量
原理就是这样,互补;
你给我的多了,现在你不够了,我通过反向边补给你;
自己画画图吧;
之后就是看代码的时间了,再看代码之前;

先感谢Fop_zz大佬的耐心教育
#include<bits/stdc++.h>
using namespace std;
const int N=1e4+5;
struct cs{int to,nxt,v;}a[N*20];
int head[N],ll=1;
int q[N],l,r;
int deep[N];
int n,m,S,E,x,y,z,ans;
void init(int x,int y,int z){a[++ll].to=y;a[ll].v=z;a[ll].nxt=head[x];head[x]=ll;}
bool bfs(){
    memset(deep,0,sizeof deep);
    q[1]=S;l=r=1;deep[S]=1;
    for(;r>=l;l++){
        int x=q[l]; if(x==E)return 1;
        for(int k=head[x];k;k=a[k].nxt)
            if(!deep[a[k].to]&&a[k].v!=0)
                deep[a[k].to]=deep[x]+1,q[++r]=a[k].to;
    }return 0;
}
int dfs(int x,int now){
    if(x==E)return now;
    int ans=0;
    for(int k=head[x];k;k=a[k].nxt)
        if(deep[a[k].to]-1==deep[x]&&a[k].v){
            int temp=dfs(a[k].to,min(a[k].v,now));
            now-=temp; ans+=temp;
            a[k].v-=temp; a[k^1].v+=temp;
            if(!now)return ans;
        }return ans;
}
int main()
{
    scanf("%d%d%d%d",&n,&m,&S,&E);
    for(int i=1;i<=m;i++){
        scanf("%d%d%d",&x,&y,&z);
        init(x,y,z);init(y,x,0);
    }
    while(bfs())ans+=dfs(S,1e9);
    printf("%d",ans);
}

搜索这种东西,玄学,不好优化;
但是,优化还是有的;

当前弧优化

对于每次dfs前,加一个cur[x]
代表x点要从第几个点开始搜;
所以再一个dfs里面每个点x他的节点只会被dfs一次;
为什么这样快;
因为那你x点dfs一遍了,x下面必然出现满流,预期直接dfs,不如先放一放,等到bfs后再去dfs;
我靠我也不知到为什么这样快QAQ

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
using namespace std;
const int N=1e4+5;
struct cs{int to,nxt,v;}a[N*20];
int head[N],ll=1,cur[N];
int q[N],l,r;
int deep[N];
int n,m,S,E,x,y,z,ans;
void init(int x,int y,int z){a[++ll].to=y;a[ll].v=z;a[ll].nxt=head[x];head[x]=ll;}
bool bfs(){
    memset(deep,0,sizeof deep);//这个其实可以优化的,我不会 
    q[1]=S;l=r=1;deep[S]=1;
    for(;r>=l;l++){//当前弧优化赋值↓ 
        int x=q[l];cur[x]=head[x];if(x==E)return 1;//发现跟就可以退出了 
        for(int k=head[x];k;k=a[k].nxt)
            if(!deep[a[k].to]&&a[k].v!=0)
                deep[a[k].to]=deep[x]+1,q[++r]=a[k].to;
    }return 0;
}
int dfs(int x,int now){//now代表当前x的可用流 
    if(x==E)return now; 
    int ans=0; 
    for(int &k=cur[x];k;k=a[k].nxt)//只访问一次 
        if(deep[a[k].to]-1==deep[x]&&a[k].v){//这个优化a[k].v会快的 
            int temp=dfs(a[k].to,min(a[k].v,now));
            now-=temp; ans+=temp;
            a[k].v-=temp; a[k^1].v+=temp;//反向边 
            if(!now)break;//优化 
        }return ans;
}
int main()
{
    scanf("%d%d%d%d",&n,&m,&S,&E);
    for(int i=1;i<=m;i++){
        scanf("%d%d%d",&x,&y,&z);
        init(x,y,z);init(y,x,0);
    }
    while(bfs())ans+=dfs(S,1e9);
    printf("%d",ans);
}
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值