网络流:有上下界的最大流

有上下界的含义: 每条管道的流量必须在范围 [L,R] 内,即在普通最大流问题上增加了下界限制。

主要分为无源点汇点和有源点汇点两类问题。

1.无源汇点有上下界的最大流

此类问题一般为:给n个点,及m根pipe,每根pipe是用来流淌液体的,单向的,每时每刻每根pipe流进来的物质要等于流出去的物质,要使得m条pipe组成一个循环体,里面流淌物质。并且满足每根pipe一定的流量限制,范围为[Li,Ri].即要满足每时刻流进来的不能超过Ri(最大流问题),同时最小不能低于Li。

解决:普通的最大流可以视作下界为0,上界为容量。所以此时让所有管道的下界变为0,上界变为R-L。但是这样的话流量不守恒(流入的不等于流出的),需要更改建图方法,寻找附加流来满足守恒。

此时添加超级源点s和汇点t,定义du[MAXN]来记录每个节点的流量情况。
du[i] = in[i] (i节点所有入流下界之和) - out[i] (i节点所有出流下界之和).
当du[i]大于0时,从 s 到 i 连一条流量为du[i]的边;
当du[i]小于0时,从 i 到 t 连一条流量为-du[i]的边;

最后对(s,t)求最大流即可,求出的最大流为附加最大流,此时每条管道的实际流量还要加上之前为了改造而修改的下界L。
当附加流最大流==所有的du[]>0之和时,有可行解(可行流)(可以使流量守恒)。

例题:Reactor Cooling(SGU-194)

//Dinic
#include<bits/stdc++.h>
#define ll long long
#define IOS std::ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
#define DEBUG cout<<",here\n";
#define MAXN 250
using namespace std;

struct EDGE
{
    ll c;//capacity
    ll flow;
    EDGE():c(0),flow(0){}
};EDGE a[MAXN][MAXN];
vector<ll>link[MAXN];
vector<pair<ll,ll> >pipe;
ll ceng[MAXN];
//注意dn[]记录每条边的下界信息,不能用点的MAXN,不然会数组太小
ll du[MAXN],dn[MAXN*MAXN*2];

ll n,m,s,t,u,v,l,c,maxn,ans=0;

ll bfs()//最大流:构建层级网络,一次bfs就行了,很简单
{
    memset(ceng,0,sizeof(ceng));
    queue<ll>q;q.push(s);
    ceng[s]=1;
    while(!q.empty()){
        ll now=q.front();q.pop();
        for(auto& i:link[now]){
            if(ceng[i]==0 && a[now][i].c-a[now][i].flow>0){
                ceng[i]=ceng[now]+1;
                q.push(i);
            }
        }
    }
    if(ceng[t])return 1;//最大流:能构建,继续之后的dfs
    return 0;//最大流:不能构建更多了,没有更多的增广路了
}

//最大流:有当前弧优化
ll dfs(ll now,ll minflow)
{
    if(now==t){ans+=minflow;return minflow;}
    ll ret=0,minflow2;
    for(auto& i:link[now]){
        if(ceng[i]==ceng[now]+1 && a[now][i].c-a[now][i].flow>0){
            minflow2=dfs(i,min(minflow,a[now][i].c-a[now][i].flow));
            if(minflow2==0)ceng[i]=0;//debug
            a[now][i].flow+=minflow2;
            a[i][now].flow-=minflow2;
            minflow-=minflow2;//最大流:剩余容量减少,以便从该节点直接再往后找增广路,提高效率,当前弧优化
            ret+=minflow2;//最大流:dfs找到的最小剩余容量之和
            if(minflow==0)break;
        }
    }
    return ret;//最大流:返回从这个节点开始之后找到的所有增广路的流量之和
}

int main()
{
    IOS
    maxn=pow(2,31);
    cin>>n>>m;
    for(ll i=1;i<=m;i++){
        cin>>u>>v>>l>>c;
        a[u][v].c+=c-l;
        du[u]-=l;du[v]+=l;dn[i-1]=l;//dn[]记录每条边的下界信息
        link[u].push_back(v);
        link[v].push_back(u);
        pipe.push_back({u,v});
    }
    s=0;t=n+1;
    ll totdu=0;
    for(ll i=1;i<=n;i++){
        if(du[i]>0){
            link[s].push_back(i);
            a[s][i].c+=du[i];
        }
        else if(du[i]<0){
            link[i].push_back(t);
            a[i][t].c+=-du[i];
        }
        if(du[i]>0)totdu+=du[i];//用于判断是否有可行解
    }

    while(bfs()){
        dfs(s,maxn);
    }

    if(ans!=totdu){cout<<"NO\n";}
    else{
        cout<<"YES\n";
        for(ll j=0;j<m;j++){
            auto &i=pipe[j];
            ll tmp=0;
            tmp+=a[i.first][i.second].flow+dn[j];//每条边的实际流量=容量下界+附加流
            cout<<tmp<<"\n";
        }
    }

    return 0;
}

2.有源汇点有上下界的最大流

源点 s ,汇点 t 。

同样,因为有下界,所以先判断是否有可行流,可参考无源汇点的可行流判断。但有源汇点如何转化为无源汇的循环流图?增设一条从 t 到 s 没有下界且容量为无穷的边 即可。之后再添加超级源汇点ss和tt,和无源汇点一样判断可行流。

当有可行流时,再原封不动地以 s,t 为源汇点跑一次最大流,结果即为最终答案。

有源汇上下界最大流只需要求解可行流并判断可行后,原封不动地进行一次最大流,这个最大流就是答案(不用再加之前的可行流流量)。因为超级源汇的边全部满流不会影响,而原可行流流量全部在t-s的反向边上,跑最大流时刚好会算进去,这样跑出来的最大流就是最终答案。

例题:【模板】有源汇上下界最大流

//Dinic
#include<bits/stdc++.h>
#define ll long long
#define IOS std::ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
#define DEBUG cout<<",here\n";
#define MAXN 1505
using namespace std;

struct EDGE
{
    ll c;//capacity
    ll flow;
    EDGE():c(0),flow(0){}
};EDGE a[MAXN][MAXN];
vector<ll>link[MAXN];
ll ceng[MAXN];
//无源汇:注意dn[]记录每条边的下界信息,不能用点的MAXN,不然会数组太小
ll du[MAXN];

ll g,C,D;

ll n,m,u,v,ans=0;
const ll INF=2147483648;

struct Dinic{
    ll s,t;
    const ll INF=2147483648;

    void addEdge(ll u,ll v,ll c){
        a[u][v].c+=c;
        link[u].push_back(v);
        link[v].push_back(u);
    }

    ll bfs()//最大流:构建层级网络,一次bfs就行了,很简单
    {
        memset(ceng,0,sizeof(ceng));
        queue<ll>q;q.push(s);
        ceng[s]=1;
        while(!q.empty()){
            ll now=q.front();q.pop();
            for(auto& i:link[now]){
                if(ceng[i]==0 && a[now][i].c-a[now][i].flow>0){
                    ceng[i]=ceng[now]+1;
                    q.push(i);
                }
            }
        }
        if(ceng[t])return 1;//最大流:能构建,继续之后的dfs
        return 0;//最大流:不能构建更多了,没有更多的增广路了
    }

    //最大流:有当前弧优化
    ll dfs(ll now,ll minflow)
    {
        if(now==t){return minflow;}
        ll ret=0,minflow2;
        for(auto& i:link[now]){
            if(ceng[i]==ceng[now]+1 && a[now][i].c-a[now][i].flow>0){
                minflow2=dfs(i,min(minflow,a[now][i].c-a[now][i].flow));
                if(minflow2==0)ceng[i]=0;//debug
                a[now][i].flow+=minflow2;
                a[i][now].flow-=minflow2;
                minflow-=minflow2;//最大流:剩余容量减少,以便从该节点直接再往后找增广路,提高效率,当前弧优化
                ret+=minflow2;//最大流:dfs找到的最小剩余容量之和
                if(minflow==0)break;
            }
        }
        return ret;//最大流:返回从这个节点开始之后找到的所有增广路的流量之和
    }

    ll maxFlow(ll s,ll t,ll minflow){
        this->s=s;this->t=t;
        ll tmp=0;
        while(bfs()){
            tmp+=dfs(s,minflow);
        }
        return tmp;
    }
};
Dinic solver;

int main()
{
    while(cin>>n)
    {
        cin>>m;
        ll dn=0;ans=0;
        ll s=0,t=n+m+1,ss=n+m+2,tt=n+m+3;
        for(ll i=0;i<=n+m+5;i++){
            du[i]=0;link[i].clear();ceng[i]=0;
            for(ll j=0;j<=n+m+5;j++){a[i][j].c=0;a[i][j].flow=0;}
        }
        /*例题特殊建图,不具代表性*/
        for(ll i=1;i<=m;i++){
            cin>>g;
            solver.addEdge(n+i,t,INF-g);
            du[n+i]-=g;du[t]+=g;
        }

        for(ll i=1;i<=n;i++){
            cin>>C>>D;
            solver.addEdge(s,i,D);

            for(ll j=1;j<=C;j++){
                ll T,L,R;
                cin>>T>>L>>R;T+=1;
                solver.addEdge(i,n+T,R-L);
                du[i]-=L;du[n+T]+=L;dn+=L;//有下界就要更改du[]
            }
        }
        /**/

        solver.addEdge(t,s,INF);//添加t到s的无穷容量边

        ll totdu=0;
        for(ll i=1;i<=t;i++){
            if(du[i]>0){
                solver.addEdge(ss,i,du[i]);
            }
            else if(du[i]<0){
                solver.addEdge(i,tt,-du[i]);
            }
            if(du[i]>0)totdu+=du[i];//无源汇:用于判断是否有可行解
        }

        ans=solver.maxFlow(ss,tt,INF);

        if(ans!=totdu){cout<<"-1\n\n";}
        else{
            //有源汇上下界最大流只需要求解可行流并判断可行后,
            //原封不动地进行一次最大流,
            //这个最大流就是答案(不用再加之前的可行流流量)。
            //因为超级源汇的边全部满流不会影响,
            //而原可行流流量全部在t-s的反向边上,
            //跑最大流时刚好会算进去,这样跑出来的最大流就是最终答案。
            ans=solver.maxFlow(s,t,INF);
            cout<<ans<<"\n\n";
        }
    }

    return 0;
}

参考:
有上下界的网络流学习笔记
网络流各种题型应用及解决方法

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值