网络流之容量限制

一.无源汇上下界可行流

题目:

我们考虑一个事情:将上界减去下界作为使得每条边满足条件,这样我们就构建出了一个满足容量限制的网络图;

但是我们似乎没有满足流量平衡,简单的举个例子:设流入u点的流量为f1,流出为f2,那么当f1!=f2,我们构建的图是存在问题的,因为u在我们构建的图中,每一条边的容量都被重新定义了,我们默认了他们都流过了最小流量。但是这个最小流量却是不守恒的;

尝试修补我们的漏洞,我们虚构两个点,源点s,汇点t,我们尝试构建补偿流,

1.对于流入大于流出(f1>f2),我们考虑一下我们构建的图的意义,当在新图中的点u,当流入为a的流量时,实际的流量为a+f1,实际的流出流量为f2+a;可以知道我们流出的少了,假设我们使得流出的a变为b,b=a+f1-f2;那么我们就需要连一条边s->u(边权为f1-f2);

2.对于流入小于流出(f1<f2),当在新图中的点u,当流入为a的流量时,实际的流量为a+f1,实际的流出流量为f2+a;可以知道我们流出的多了,那么我们需要一条边来进行分担流出的,那么我们就需要连一条边u->t(边权为f2-f1);

我们可以知道的一点是,源点和汇点的边权和tot是一样的,当汇点和源点的边权全部流满时,才会对应于原图的可行流(因为我们需要满足补偿),所以我们采用dinic求最大流,看最大流是否为tot

实现代码如下:

#include <iostream>
#include <cstring>
#include <queue>
using namespace std;

const int N=210,M=(10210+N)*2,INF=1e9;
int n,m,S,T;
int h[N],ne[M],e[M],idx,f[M],C[M];
int s[N],d[N],cur[N];
void add(int a, int b, int c, int d){
    e[idx]=b;f[idx]=d-c;C[idx]=c;ne[idx]=h[a];h[a]=idx++;
    e[idx]=a;f[idx]=0;ne[idx]=h[b];h[b]=idx++;
}

bool bfs(){
    memset(d,-1,sizeof d);
    d[S]=0;cur[S]=h[S];
    queue<int>q;
    q.push(S);
    while(!q.empty()){
        int t=q.front();
        q.pop();
        for(int i=h[t]; ~i; i=ne[i]){
            int ver=e[i];
            if(d[ver]==-1 && f[i]){
                d[ver]=d[t]+1;
                cur[ver]=h[ver];
                if(ver==T)return true;
                q.push(ver);
            }
        }
    }
    return false;
}

int find(int u, int limit){
    if(u==T)return limit;
    int flow=0;
    for(int i=cur[u]; ~i && flow<limit; i=ne[i]){
        int ver=e[i];
        cur[u]=i;
        if(d[ver]==d[u]+1 && f[i]){
            int t=find(ver,min(f[i],limit-flow));
            if(!t)d[ver]=-1;
            f[i]-=t;f[i^1]+=t;flow+=t;
        }
    }
    return flow;
}

int dinic(){
    int res=0,flow=0;
    while(bfs())while(flow=find(S,INF))res+=flow;
    return res;
}
int main(){
    cin>>n>>m;
    S=0;T=n+1;
    memset(h,-1,sizeof h);
    for(int i=0; i<m; i++){
        int a,b,c,d;
        cin>>a>>b>>c>>d;
        add(a,b,c,d);
        s[a]-=c;s[b]+=c;//流入流出
    }
    int tot=0;
    for(int i=1; i<=n; i++){
        //流入大
        //cout<<s[i]<<endl;
        if(s[i]>0)add(S,i,0,s[i]),tot+=s[i];
        else add(i,T,0,-s[i]);
    }
   // cout<<tot<<endl;
    if(dinic()!=tot)puts("NO");
    else{
        puts("YES");
        for(int i=0; i<2*m; i+=2){
            cout<<f[i^1]+C[i]<<endl;
        }
    }
}

二.有源汇上下界最大流

题目:

 我们首先考虑一个问题,无源汇上下界的最大流如何求?我们可以由题目一可以知道,我们构建的dinic最大流实际上是他的一个可行流(不一定是最大的),我们考虑那么一个事情,假设图中存在两个点s,t;我们可以知道我们是无法从s出发经过S或者T到达t的;也就是说当我们构建出可行流后,以s点开始跑dinic,一定不会影响到S和T的边权的,那么S和T的边权始终是满流状态(也就是说,我们从s点开始跑dinic,最终的图对应到原图还是一个可行流),于是我们可跑dinic,从而求出s->t的最大流,注意到s和t是循环的,所以我们需要断掉他们其中的一条边,并且记下边权res;

跑完所以最终的答案为res+dinic;

问题回到,有源汇上下界的最大流,我们考虑额外连接一条边t->s,边权为无穷大,那么我们就得到了一个无源汇上下界的最大流求解的题目(要求源点和汇点为s,t),于是我们可以直接套用上面的分析求解即可;

最重要的一点是:清楚S,T的边权在构建出来后就不会再改变(后面的最小流也是一样的道理)

代码如下:

#include <iostream>
#include <cstring>
#include <queue>
using namespace std;

const int N=210,M=(10210+N)*2,INF=1e9;
int n,m,S,T;
int h[N],ne[M],e[M],idx,f[M],C[M];
int s[N],d[N],cur[N];
void add(int a, int b, int c, int d){
    e[idx]=b;f[idx]=d-c;C[idx]=c;ne[idx]=h[a];h[a]=idx++;
    e[idx]=a;f[idx]=0;ne[idx]=h[b];h[b]=idx++;
}

bool bfs(){
    memset(d,-1,sizeof d);
    d[S]=0;cur[S]=h[S];
    queue<int>q;
    q.push(S);
    while(!q.empty()){
        int t=q.front();
        q.pop();
        for(int i=h[t]; ~i; i=ne[i]){
            int ver=e[i];
            if(d[ver]==-1 && f[i]){
                d[ver]=d[t]+1;
                cur[ver]=h[ver];
                if(ver==T)return true;
                q.push(ver);
            }
        }
    }
    return false;
}

int find(int u, int limit){
    if(u==T)return limit;
    int flow=0;
    for(int i=cur[u]; ~i && flow<limit; i=ne[i]){
        int ver=e[i];
        cur[u]=i;
        if(d[ver]==d[u]+1 && f[i]){
            int t=find(ver,min(f[i],limit-flow));
            if(!t)d[ver]=-1;
            f[i]-=t;f[i^1]+=t;flow+=t;
        }
    }
    return flow;
}

int dinic(){
    int res=0,flow=0;
    while(bfs())while(flow=find(S,INF))res+=flow;
    return res;
}
int main(){
    int ss,tt;
    cin>>n>>m>>ss>>tt;
    S=0;T=n+1;
    memset(h,-1,sizeof h);
    for(int i=0; i<m; i++){
        int a,b,c,d;
        cin>>a>>b>>c>>d;
        add(a,b,c,d);
        s[a]-=c;s[b]+=c;//流入流出
    }
    int tot=0;
    for(int i=1; i<=n; i++){
        //流入大
        //cout<<s[i]<<endl;
        if(s[i]>0)add(S,i,0,s[i]),tot+=s[i];
        else add(i,T,0,-s[i]);
    }
    add(tt,ss,0,INF);
   // cout<<tot<<endl;
    if(dinic()!=tot)puts("No Solution");
    else{
        int res=f[idx-1];
        f[idx-1]=f[idx-2]=0;
        S=ss,T=tt;
        cout<<res+dinic()<<endl;
    }
}

三.有源汇上下界最小流

题目:

首先我们知道在跑完虚拟源汇点S,T的dinic后,再对其中的任何点跑dinic,得到的网络流依旧是一个可行流。不妨我们考虑,求s->t的最小流,首先我们知道初始流为res(S,T跑dinic得到的),我们考虑求出t->s的最大流,那么通过逆向,于是我们就得到了s->t的最小流为res-dinic(t->s);

代码如下:

#include <iostream>
#include <cstring>
#include <queue>
using namespace std;

const int N=50020,M=(125003+N)*2,INF=1e9;
int n,m,S,T;
int h[N],ne[M],e[M],idx,f[M],C[M];
int s[N],d[N],cur[N];
void add(int a, int b, int c, int d){
    e[idx]=b;f[idx]=d-c;C[idx]=c;ne[idx]=h[a];h[a]=idx++;
    e[idx]=a;f[idx]=0;ne[idx]=h[b];h[b]=idx++;
}

bool bfs(){
    memset(d,-1,sizeof d);
    d[S]=0;cur[S]=h[S];
    queue<int>q;
    q.push(S);
    while(!q.empty()){
        int t=q.front();
        q.pop();
        for(int i=h[t]; ~i; i=ne[i]){
            int ver=e[i];
            if(d[ver]==-1 && f[i]){
                d[ver]=d[t]+1;
                cur[ver]=h[ver];
                if(ver==T)return true;
                q.push(ver);
            }
        }
    }
    return false;
}

int find(int u, int limit){
    if(u==T)return limit;
    int flow=0;
    for(int i=cur[u]; ~i && flow<limit; i=ne[i]){
        int ver=e[i];
        cur[u]=i;
        if(d[ver]==d[u]+1 && f[i]){
            int t=find(ver,min(f[i],limit-flow));
            if(!t)d[ver]=-1;
            f[i]-=t;f[i^1]+=t;flow+=t;
        }
    }
    return flow;
}

int dinic(){
    int res=0,flow=0;
    while(bfs())while(flow=find(S,INF))res+=flow;
    return res;
}
int main(){
    int ss,tt;
    cin>>n>>m>>ss>>tt;
    S=0;T=n+1;
    memset(h,-1,sizeof h);
    for(int i=0; i<m; i++){
        int a,b,c,d;
        cin>>a>>b>>c>>d;
        add(a,b,c,d);
        s[a]-=c;s[b]+=c;//流入流出
    }
    int tot=0;
    for(int i=1; i<=n; i++){
        //流入大
        //cout<<s[i]<<endl;
        if(s[i]>0)add(S,i,0,s[i]),tot+=s[i];
        else add(i,T,0,-s[i]);
    }
    add(tt,ss,0,INF);
    if(dinic()!=tot)puts("No Solution");
    else{
        int res=f[idx-1];
        f[idx-1]=f[idx-2]=0;
        S=tt,T=ss;
        cout<<res-dinic()<<endl;
    }
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值