蒟蒻与有上下界网络流的约会

题外话

这是一个风和日丽,月朗风清的上午。
蒟蒻坐在机房里敲着代码,听着远处教学楼传来的开考哨,感觉心情一阵愉悦。
于是她决定学一下有上下界的网络流。
loj是一个好oj,你可以找到下面三种模型的模板题(但是本蒟蒻附的代码并不是loj上板子题的代码,因为在本蒟蒻发现loj上有板子题的时候,已经快到中午吃饭的时间了)

无源汇上下界可行流

分析

这类问题是这样的:给你一个网络,每条边i上的流量都必须满足 lowiflowimaxi ,问流量是否可以在其中循环流动?
首先我们让所有边的流量都为 low ,当然,这样子流量进出不平衡。
对于每一个点,我们记:“囤积其上”的流量为du。如果在当前网络上,这个点流出的流量大于流入的流量,du为正值。这个点流出的流量小于流入的流量,du为负值。
现在我们要继续往这张图里“灌水”,使得所有点进出平衡。也就是说,如果一个点的du为正值,就需要额外多流出du这么多的流量。反之,要额外流入这么多流量。
然而我们好像不能从算法上达成寻找进出不平衡的流的效果…
那么便从建图上搞吧!我们可以建立一个虚拟源点ss和一个虚拟汇点tt。对于一个du为正值的点i,我们可以连一条边 (ss,i,du) ,为其灌入du的流量好使其流出这么多。否则,连边 (i,tt,du)
可以发现,如果原图能够构建一个合法流量网络,则此时跑完网络流后,所有与虚拟源点或虚拟汇点连接的边都应该满流。

代码

zoj2314
题目大意:有一个核反应堆,每条边流量的限制为[l,r],求是否可以循环流动。如果可以,输出每条边的流量。

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
const int N=205,M=205*205*2+205,inf=0x3f3f3f3f;
int T,ss,tt,n,m,tot;
int h[N],ne[M],to[M],flow[M],du[N],lev[N],q[N],low[M];
void add(int x,int y,int z) {
    to[++tot]=y,ne[tot]=h[x],h[x]=tot,flow[tot]=z;
    to[++tot]=x,ne[tot]=h[y],h[y]=tot,flow[tot]=0;
}
int dfs(int x,int liu) {
    if(x==tt) return liu;
    int kl,sum=0;
    for(int i=h[x];i;i=ne[i])
        if(flow[i]>0&&lev[to[i]]==lev[x]+1) {
        kl=dfs(to[i],min(flow[i],liu-sum));
        sum+=kl,flow[i]-=kl,flow[i^1]+=kl;
        if(sum==liu) return sum;
    }
    if(!sum) lev[x]=-1;
    return sum;
}
int bfs() {
    for(int i=ss;i<=tt;++i) lev[i]=0;
    int x,he=1,ta=1; q[1]=ss,lev[ss]=1;
    while(he<=ta) {
        x=q[he],++he;
        if(x==tt) return 1;
        for(int i=h[x];i;i=ne[i])
            if(flow[i]>0&&!lev[to[i]])
            lev[to[i]]=lev[x]+1,q[++ta]=to[i];
    }
    return 0;
}
int main()
{
    int x,y,xj,sj,flag;
    scanf("%d",&T);
    while(T--) {
        scanf("%d%d",&n,&m);
        tot=1,ss=0,tt=n+1;
        memset(h,0,sizeof(h)),memset(du,0,sizeof(du));
        for(int i=1;i<=m;++i) {
            scanf("%d%d%d%d",&x,&y,&xj,&sj);
            add(x,y,sj-xj),du[x]-=xj,du[y]+=xj,low[i]=xj;
        }
        for(int i=1;i<=n;++i)
            if(du[i]>0) add(ss,i,du[i]);
            else if(du[i]<0) add(i,tt,-du[i]);
        while(bfs()) dfs(ss,inf);
        flag=0;
        for(int i=h[ss];i;i=ne[i]) if(flow[i]) {flag=1;break;}
        if(flag) puts("NO");
        else {
            puts("YES");
            for(int i=1;i<=m;++i) printf("%d\n",low[i]+flow[i*2+1]);
        }
        if(T) putchar('\n');
    }
    return 0;
}

有源汇上下界最大流

例题: zoj3229
题目大意:
大jay形是一个哲♂学日报的主编,他的任务是每天拍摄一些可爱的女孩子们并把照片刊登在日报上。他的日报要办n天,一共有m个可爱的女孩子。
大jay形每天可以拍 di 张照片,当天有 ci 个可爱的女孩子有空让他拍摄,但是他拍某个女孩子的照片数必须在区间 [lj,rj] 间。在日报办完后,对于某个女孩子j,大jay形刊登的她的照片数要大于等于 gj ,求大jay形是否可以达成任务,如果可以,最多可以拍多少张照♂片。
题目分析:
建模应该很直观,每天建一个点,每个女孩子建一个点。s向每天连一条流量为 [0,di] 的边,每个女孩子向汇点t连一条流量为 [gj,inf] 的边,然后每一天的点向当天可以拍摄的女孩子连一条流量为 [lj,rj] 的边。
现在开始检测是否有合法方案。由于源点和汇点并不满足流量进出平衡,所以似乎很难向上一题那样判断可行性。所以我们做一个操作:从t向s连一条流量为inf的额外边,这样就可以进出平衡了。然后仿照无源汇的方法搞一通。
最后,我们去掉虚拟源汇,再跑一次最大流,得到在满足下界情况下尽可能填满流量网络的最大流。
另外,此题的评测挂了2年了,所以本蒟蒻并不知道自己的代码对不对,先贴着吧。如果你想知道自己的代码对不对,可以去试一试loj上的板子题。

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
const int N=1400,M=200005,inf=0x3f3f3f3f;
int n,m,tot,ans;
int h[N],ne[M],to[M],flow[M],du[N],lev[N],q[N],low[M],can[N];
void add(int x,int y,int z) {
    to[++tot]=y,ne[tot]=h[x],h[x]=tot,flow[tot]=z;
    to[++tot]=x,ne[tot]=h[y],h[y]=tot,flow[tot]=0;
}
int dfs(int x,int liu,int t) {
    if(x==t) return liu;
    int kl,sum=0;
    for(int i=h[x];i;i=ne[i])
        if(flow[i]>0&&lev[to[i]]==lev[x]+1) {
        kl=dfs(to[i],min(flow[i],liu-sum),t);
        flow[i]-=kl,flow[i^1]+=kl,sum+=kl;
        if(sum==liu) return sum;
    }
    if(!sum) lev[x]=-1;
    return sum;
}
int bfs(int s,int t) {
    for(int i=0;i<=n+m+3;++i) lev[i]=0;
    int he=1,ta=1,x; q[1]=s,lev[s]=1;
    while(he<=ta) {
        x=q[he],++he;
        if(x==t) return 1;
        for(int i=h[x];i;i=ne[i])
            if(flow[i]>0&&!lev[to[i]])
            lev[to[i]]=lev[x]+1,q[++ta]=to[i];
    }
    return 0;
}
int check() {
    int ss=n+m+2,tt=n+m+3;
    for(int i=0;i<=n+m+1;++i)
        if(du[i]>0) add(ss,i,du[i]);
        else if(du[i]<0) add(i,tt,-du[i]);
    while(bfs(ss,tt)) dfs(ss,inf,tt);
    for(int i=h[ss];i;i=ne[i]) if(flow[i]) return 0;
    return 1;
}
int main()
{
    int x,num,e_js,r;
    while(~scanf("%d%d",&n,&m)) {
        tot=1,ans=0; int S=0,T=n+m+1;
        memset(h,0,sizeof(h)),memset(du,0,sizeof(du));
        for(int i=1;i<=m;++i) scanf("%d",&x),du[T]+=x,du[i+n]-=x;
        e_js=0;
        for(int i=1;i<=n;++i) {
            scanf("%d%d",&num,&can[i]);
            for(int j=1;j<=num;++j) {
                scanf("%d%d%d",&x,&low[++e_js],&r);
                add(i,x+n+1,r-low[e_js]);
                du[x+n+1]+=low[e_js],du[i]-=low[e_js];
            }
        }
        for(int i=1;i<=n;++i) add(S,i,can[i]);
        for(int i=1;i<=m;++i) add(i+n,T,inf);
        add(T,S,inf);
        if(check()) {
            while(bfs(S,T)) ans+=dfs(S,inf,T);//由于(T,S,inf)的存在,会回流,所以可以得到最大流?
            printf("%d\n",ans);
            for(int i=1;i<=e_js;++i)
                printf("%d\n",low[i]+flow[i*2+1]);
        }
        else puts("-1");
        puts("");
    }
    return 0;
}

有源汇上下界最小流

分析

问题模型在标题里应该已经很直观了desi
事实上,我们跑出来的可行流并不一定是最小流。所以,跑完可行流后要怎么办呢?
当然是——求T到S的最大流!
显然,根据网络流反向边的定义,这样子当然可以尽可能多地减少流量。而由于我们跑可行流的时候,用于确认可行(即与虚拟源汇相连的边)是游离于整张图之外的,所以不会导致不可行。

代码

bzoj2502 (这是一道权限题,没有权限的孩子可以选择loj上的板子题)

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
const int N=205,M=4e6,inf=0x3f3f3f3f;
int h[N],ne[M],to[M],flow[M],lev[N],q[N],du[N];
int n,m,tot=1,ans;
void add(int x,int y,int z) {
    to[++tot]=y,ne[tot]=h[x],h[x]=tot,flow[tot]=z;
    to[++tot]=x,ne[tot]=h[y],h[y]=tot,flow[tot]=0;
}
void del(int x) {for(int i=h[x];i;i=ne[i]) flow[i]=flow[i^1]=0;}
int dfs(int x,int liu,int t) {
    if(x==t) return liu;
    int kl,sum=0;
    for(int i=h[x];i;i=ne[i])
        if(flow[i]>0&&lev[to[i]]==lev[x]+1) {
        kl=dfs(to[i],min(flow[i],liu-sum),t);
        sum+=kl,flow[i]-=kl,flow[i^1]+=kl;
        if(sum==liu) return sum;
    }
    if(!sum) lev[x]=-1;
    return sum;
}
int bfs(int s,int t) {
    for(int i=0;i<=n+3;++i) lev[i]=0;
    int he=1,ta=1,x; q[1]=s,lev[s]=1;
    while(he<=ta) {
        x=q[he],++he;
        if(x==t) return 1;
        for(int i=h[x];i;i=ne[i])
            if(flow[i]>0&&!lev[to[i]])
            lev[to[i]]=lev[x]+1,q[++ta]=to[i];
    }
    return 0;
}
int main()
{
    int num,x,S,T,ss,tt;
    scanf("%d",&n);S=0,T=n+1,ss=n+2,tt=n+3;
    for(int i=1;i<=n;++i) {
        scanf("%d",&num);
        for(int j=1;j<=num;++j) {
            scanf("%d",&x);
            ++du[x],--du[i],add(i,x,inf);
        }
    }
    for(int i=1;i<=n;++i) {
        add(S,i,inf),add(i,T,inf);
        if(du[i]>0) add(ss,i,du[i]);
        else if(du[i]<0) add(i,tt,-du[i]);
    }
    add(T,S,inf);
    while(bfs(ss,tt)) dfs(ss,inf,tt);
    ans=flow[tot];
    del(ss),del(tt),flow[tot]=flow[tot-1]=0;
    while(bfs(T,S)) ans-=dfs(T,inf,S);
    printf("%d",ans);
    return 0;
}

上下界最小费用可行流

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值