上下界网络流

最近几天学习了上下界网络流,感觉不太懂,但至少学会了如何建图。

总的来说,分为以下三个问题

  • 无源汇最大流

  • 有源汇最大流

  • 有源汇最小流
  • 一、无源汇最大流

    大致的做法是这样的:
    首先构成一个附加网络:将图的下界分离到一个附加源和汇中,而上界则变为原弧的上界减去下界的差。
    再在附加源汇上跑一次最大流即可。记录每个点的入流下界和减去出流下界和,当下界和>0时怎连入一条源s到这个点的边,上界为这个下界和,下界为0;当下界和

    #include<bits/stdc++.h>
    using namespace std;
    #define maxn 500
    #define oo ~0u>>1
    #define maxm 80810
    int S,T,cap[maxm],d[maxn],cur[maxn],gap[maxn],p[maxn];
    int N,ihead[maxn],inext[maxm],to[maxm],first[maxm],cnt;
    int dn[maxm],du[maxn],from[maxm];
    int isap(){
        int flow=0,u,v,i,f=oo;
        memset(d,0,sizeof(d));
        memset(gap,0,sizeof(gap));
        for(i=1;i<=N;i++)
            {cur[i]=ihead[i];}
        u=S;gap[0]=N;
        while(d[S]<N){
            for(i=cur[u];i;i=inext[i]){
    
                if(cap[i]&&d[u]==d[to[i]]+1)
                    break;}
            if(i){
                cur[u]=i,v=to[i],f=min(f,cap[i]);
                p[v]=i,u=v;
                if(v==T){
                    for(;v!=S;v=from[p[v]])
                        cap[p[v]]-=f,cap[p[v]^1]+=f;
                    u=S,flow+=f;
                    f=oo;
                }
            }
            else{
                if(!(--gap[d[u]]))
                    break;
                d[u]=N;
                cur[u]=ihead[u];
                for(i=cur[u];i;i=inext[i])
                    if(cap[i]&&d[u]>d[to[i]+1])
                        d[u]=d[to[i]]+1;
                gap[d[u]]++;
                if(u!=S)
                    u=from[p[u]];
            }
        }
        return flow;
    }
    void add_edge(int u,int v,int c){
        inext[++cnt]=ihead[u];
        ihead[u]=cnt;
        from[cnt]=u;
        to[cnt]=v;
        cap[cnt]=c;
        inext[++cnt]=ihead[v];
        ihead[v]=cnt;
        from[cnt]=v;
        to[cnt]=u;
        cap[cnt]=0;
    }
    int main(){
        int n,m,u,v,down,up,i,flag;
        while(cin>>n>>m){
            S=n+1;T=n+2;N=n+2;cnt=1;
            memset(ihead,0,sizeof(ihead));
            memset(du,0,sizeof(du));
            memset(dn,0,sizeof(dn));
            for(int i=1;i<=m;i++){
                cin>>u>>v>>down>>up;
                add_edge(u,v,up-down);
                du[u]-=down;du[v]+=down;
                dn[i]=down;
            }
            for(int i=1;i<=n;i++){
                if(du[i]>0)
                    add_edge(S,i,du[i]);
                if(du[i]<0)
                    add_edge(i,T,-du[i]);
            }
            isap();
            flag=1;
            for(i=ihead[S];i;i=inext[i])
                if(cap[i]){
                    flag=0;
                    break;
                }
            if(!flag)
                cout<<"NO"<<'\n';
            else{
                cout<<"YES"<<'\n';
                for(i=1;i<=m;i++)
                    cout<<cap[(i<<1)+1]+dn[i]<<'\n';
            }
        }
    }



    二、有源汇最大流

    大致的做法是这样的(一样的开头):
    从汇t连一条弧到源s,上界为无限大,下界为0。
    再设一个超级源ss和一个超级汇tt,按照无源汇上下界最大流的方法连边给ss和tt。
    从ss跑最大流到tt,判断是否有弧满后,去掉超级源和汇及其边,再跑一次s-t的最大流(注意这里最大流绝不是t-s的反向弧,因为在第二次跑最大流的时候将这弧退掉了),最后根据题目要求输出即可。

    
    #include<bits/stdc++.h>
    using namespace std;
    #define CC(a, c) memset(a, c, sizeof(a))
    const int maxn=1510, oo=100000000, maxm=151000;
    int cap[maxm], d[maxn], cur[maxn], gap[maxn], p[maxn];
    int ihead[maxn], inext[maxm], to[maxm], from[maxm], cnt;
    int inout[maxn], down[400][1200], id[400][1200];
    int E[maxm][2], e;
    int isap(int s, int t, int n)
    {
        int flow=0, i, u, f=oo;
        CC(gap, 0);
        CC(d, 0);
        for(i=0;i<=n;i++) cur[i]=ihead[i];
        u=s;
        gap[0]=n;
        while(d[s]<n) {
            for(i=cur[u]; i; i=inext[i]) if(cap[i] && d[u]==d[to[i]]+1) break;
            if(i) {
                cur[u]=p[to[i]]=i;u=to[i];
                if(u==t) {
                    for(f=oo; u!=s; u=from[p[u]]) f=min(f, cap[p[u]]);
                    for(u=t; u!=s; u=from[p[u]]) cap[p[u]]-=f, cap[p[u]^1]+=f;
                    flow+=f;
                }
            } else {
                if(!(--gap[d[u]]) ) break;
                d[u]=n;
                cur[u]=ihead[u];
                for(i=cur[u]; i; i=inext[i]) if(cap[i] && d[u]>d[to[i]]+1)
                        d[u]=d[to[i]]+1;
                gap[d[u]]++;
                if(u!=s) u=from[p[u]];
            }
        }
        return flow;
    }
    
    void add(int u, int v, int c){
        inext[++cnt]=ihead[u];ihead[u]=cnt;
        from[cnt]=u;to[cnt]=v;cap[cnt]=c;
        inext[++cnt]=ihead[v];ihead[v]=cnt;
        from[cnt]=v;to[cnt]=u;cap[cnt]=0;
    }
    void init()
    {
        CC(ihead,0);cnt=1;CC(down,0);
        CC(id,0);CC(inout,0);e=0;
    }
    int main(){
        int n,m,i,g,d,t,l,r,sum,S,T;
        while(cin>>n>>m){
            init();
            S=n+m+1;T=n+m+2;
            for(i=1;i<=m;i++) {
                cin>>g;
                add(i+n,T,oo-g);
                inout[i+n]-=g;inout[T]+=g;
            }
            for(int i=1;i<=n;i++){
                cin>>g>>d;
                add(S, i, d);
                while(g--) {
                    cin>>t>>l>>r;
                    inout[i]-=l;inout[t+1+n]+=l;add(i,t+1+n,r-l);
                    down[i][t+1]+=l;id[i][t+1]=cnt;E[++e][0]=i;E[e][1]=t+1;
                }
            }
            add(T,S,oo);
            S=n+m+3;T=n+m+4;
            sum=0;
            for(i=1;i<=n+m+2;i++){
                if(inout[i]>0) {
                    sum+=inout[i];
                    add(S,i,inout[i]);
                }
                if(inout[i]<0) add(i,T,-inout[i]);
            }
            if(isap(S, T, T)!=sum) cout<<-1<<'\n';
            else {
                ihead[S]=ihead[T]=0;S=n+m+1;T=n+m+2;
                cout<<isap(S,T,T+2)<<'\n';
                for(i=1;i<=e;i++)
                    cout<<down[E[i][0]][E[i][1]]+cap[id[E[i][0]][E[i][1]]]<<'\n';
            }
            cout<<'\n';
        }
    }
    

    三、有源汇最小流

    大致的做法是这样的:

    先构造附加网络到超级源和超级汇中,然后跑超级源到超级汇的最大流,再连汇t到源s上界为无限大下界为0的弧,再跑一次超级源到超级汇的最大流即可,最小流就是t-s的反向弧。

    总的来说大概就是这样了。


    一些代码和资料放在了Server上了。


    参考:《一种简易的方法求解流量有上下界的网络中网络流问题》周源

    也有放在Server上,链接同上

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值