上下界網絡流

一:概述

一般的網絡流模型中對邊只有上界的限制,及流過該邊的流量不超過上界。上下界網絡流的不同之處在於,每條邊還有一個下界,即流過該邊的流量不少於下界。其實一般的網絡流也可以看做下界爲0。

上下界網絡流的模型可以分爲以下幾種類型:
1.無源匯的上下界可行流:沒有源點和匯點,要求一組滿足流量限制的可行流。
2.有源匯的上下界最大流:有源點和匯點,求滿足流量限制的最大流。
3.有源匯的上下界最小流:有源點和匯點,求滿足流量限制的最小流。
4.上下界費用流:每條邊有費用,在上面3中類型的基礎上使得總費用最小。

二:構圖技巧

無論是哪種類型,首先要解決的是下界的限制。

對於一條邊u->v,下界爲l,上界爲r。我們可以將這條邊變爲上界爲r-l,下界爲0。相當於在可行流的基礎上每條邊的流量都減小了l。但是這樣做了之後,流量卻不守恆了。流入v的流量少了l,流出u的流量也少了l。這時我們設置超級源點S和超級匯點T,從S向v連一條流量爲l的邊,從u到T連一條流量爲l的邊,來平衡流量。我們稱這樣的邊爲附加邊。

其實我更習慣這樣鏈附加邊:記錄一個數組d,對於一條邊u->v,下界爲l,d[v]+=l,d[u]-=l。然後掃描所有點,如果d[i]>0說明i需要流入d[i]的流量,所以從S向i連一條流量爲d[i]的邊,如果d[i]<0說明i需要流出-d[i]的流量,所以從i向T連一條流量爲-d[i]的邊。這樣邊數會少一些,跑起來就快一些。

對於不同的類型還有不同的處理技巧:

1.無源匯的上下界可行流

按上面的方式建邊後,跑一遍S到T的最大流。檢查所有附加邊是否滿流,若滿流則存在可行流,否則不存在。
若存在可行流,一組可行解就是每條邊流過的流量加上流量下界。

一道例題:zoj2314

題目大意:給n個點,及m根管子,每根管子可單向運輸液體,每時每刻每根管子流進來的物質要等於流出去的物質,m條管子組成一個循環體,裏面流躺物質。並且滿足每根管子一定的流量限制,範圍爲[Li,Ri].即要滿足每時刻流進來的不能超過Ri,同時最小不能低於Li。問是否可行,可行則輸出一組可行解。

思路:按上面的方式建邊直接跑就行了。

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<cmath>
#include<queue>
#include<vector>
using namespace std;

#define N 21000
struct edge{
    int x,d,next;
}e[1000100];
int first[N],tot,du[N],down[N],dis[N],Q[N],cur[N];
int z,p,q,x,y,n,m,s,t;

void add(int x,int y,int z){
    e[++tot].x=y;
    e[tot].next=first[x];
    e[tot].d=z;
    first[x]=tot;
}
void init(){
    scanf("%d%d",&n,&m);
    tot=1; memset(du,0,sizeof(du));
    memset(first,0,sizeof(first));
    s=n+1; t=n+2;
    for(int i=1;i<=m;i++){
        scanf("%d%d%d%d",&x,&y,&down[i],&z);
        add(x,y,z-down[i]); add(y,x,0);
        du[x]-=down[i]; du[y]+=down[i];
    }
    for(int i=1;i<=n;i++) 
    if(du[i]<0) add(i,t,-du[i]),add(t,i,0);
    else add(s,i,du[i]),add(i,s,0);
}
bool  bfs(){
    memset(dis,-1,sizeof(dis));
    Q[p=q=1]=s; dis[s]=0;
    for(;p<=q;p++){
        int x=Q[p];
        for(int i=first[x];i;i=e[i].next)
        if(e[i].d&&dis[e[i].x]==-1){
            dis[e[i].x]=dis[x]+1;
            Q[++q]=e[i].x;
        }
    }
    if(dis[t]==-1) return 0;
    return 1;
}
int dfs(int x,int y){
    if(x==t) return y;
    int sum=0,tmp;
    for(int i=cur[x];i;i=e[i].next)
    if(e[i].d&&dis[e[i].x]==dis[x]+1){
        tmp=dfs(e[i].x,min(e[i].d,y-sum));
        sum+=tmp; e[i].d-=tmp; e[i^1].d+=tmp;
        if(e[i].d) cur[x]=i;
        if(sum==y) return sum;
    }
    if(sum==0) dis[x]=-1;
    return sum;
}
int dinic(){
    int ans=0;
    while(bfs()){
        for(int i=1;i<=t;i++) cur[i]=first[i];
        ans+=dfs(s,1000000007);
    }
    for(int i=first[s];i;i=e[i].next)
    if(e[i].d) return puts("NO"),ans;
    puts("YES");
    for(int i=2;i<=2*m;i+=2) 
    printf("%d\n",e[i+1].d+down[i/2]);
    return ans;
}
int main(){
    int T; 
    scanf("%d",&T);
    while(T--){
        init();
        dinic();
        putchar('\n');
    }
    return 0;
}

2.有源匯的上下界最大流

源點和匯點是不滿足流量平衡的,不能直接套用上面的方法。我們可以先將匯點向源點連一條上界爲正無窮,下界爲0的邊。將源點和匯點變爲滿足流量平衡的普通點,再按上面的方式建邊後,跑一遍S到T的最大流。若附加邊未滿流則無解。
跑完一邊後,我們將每條邊的下界滿足了,但這還不是最大流。因爲這是原圖中還有流量可流。我們將附加邊和匯點向源點連的邊刪去,再跑一次最大流。這次就是真正的最大流了。

例題:zoj3229

題目大意:一個人給m個人拍照,計劃拍照n天,每一天最多個C個人拍照,每天拍照數不能超過D張,而且給每個人i拍照有數量限制[Li,Ri],對於每個人n天的拍照總和不能少於Gi,如果有解求屌絲最多能拍多少張照,並求每天給對應女神拍多少張照;否則輸出-1。

解題思路:增設一源點s,匯點t,s到第i天連一條上界爲Di下界爲0的邊,每個人到匯點連一條下界爲Gi上界爲無窮的邊,對於每一天,當天到第i個人連一條[Li,Ri]的邊。按上面的方法構圖就行了。

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<cmath>
#include<queue>
#include<vector>
using namespace std;

#define INF 1000000007
struct edge{
    int x,data,next,id;
}e[1000000];
int first[2000],dis[2000],du[2000],ans[100000];
int s,t,tot,ss,sum,num,tt,x,y,d,c,n,m;

void add(int x,int y,int d,int id){
    e[++tot].x=y;
    e[tot].data=d;
    e[tot].id=id;
    e[tot].next=first[x];
    first[x]=tot;
    if(~tot&1) add(y,x,0,id);
}
bool bfs(){
    int c[2000],p,q;
    memset(dis,-1,sizeof(dis));
    dis[c[1]=s]=1;
    for(p=q=1;p<=q;p++)
     for(int i=first[c[p]];i;i=e[i].next)
     if(e[i].data&&dis[e[i].x]==-1){
        dis[e[i].x]=dis[c[p]]+1;
        c[++q]=e[i].x;
     }
    return dis[t]!=-1;
}
int dfs(int x,int y){
    if(x==t||y==0) return y;
    int tmp,rec=0;
    for(int i=first[x];i;i=e[i].next)
    if(e[i].data&&dis[e[i].x]==dis[x]+1){
        tmp=dfs(e[i].x,min(e[i].data,y-rec));
        rec+=tmp; e[i].data-=tmp; e[i^1].data+=tmp;
        if(rec==y) return rec;
    }
    if(rec==0) dis[x]=-1;
    return rec;
}
int dinic(){
    int ans=0;
    while(bfs()) ans+=dfs(s,INF);
    return ans;
}
void solve(){
    ss=n+m+1; tt=n+m+2;
    for(int i=1;i<=m;i++){
        scanf("%d",&x);
        du[i+n]-=x; du[tt]+=x;
        add(i+n,tt,INF-x,0);
    }
    for(int i=1;i<=n;i++){
        scanf("%d%d",&c,&d);
        add(ss,i,d,0);
        for(int j=1;j<=c;j++){
            scanf("%d%d%d",&d,&x,&y);
            du[i]-=x; du[n+d+1]+=x;
            ans[++num]=x;
            add(i,n+d+1,y-x,num);
        }
    }
    s=n+m+3; t=n+m+4;
    for(int i=1;i<=n+m+2;i++)
    if(du[i]>0) add(s,i,du[i],0);
    else add(i,t,-du[i],0);
    add(tt,ss,INF,0);
    dinic();
    for(int i=first[s];i;i=e[i].next)
    if(e[i].data) {puts("-1"); return;}
    first[s]=first[t]=0;
    s=ss; t=tt;
    sum=dinic();
    printf("%d\n",sum);
    for(int i=3;i<=tot;i+=2)
    ans[e[i].id]+=e[i].data;
    for(int i=1;i<=num;i++) printf("%d\n",ans[i]);
}

int main(){
    while(~scanf("%d%d",&n,&m)){
        memset(ans,0,sizeof(ans));
        memset(first,0,sizeof(first));
        memset(du,0,sizeof(du));
        tot=1; num=0;
        solve();
        puts("");
    }
    return 0;
}

有源匯的上下界最小流

和有源匯的上下界最大流的構圖相同。但是跑完第一次最大流時的答案並不是最小流。應爲他只是滿足了網絡的下界,而網絡中可能存在環,這樣部分的流量我們沒有充分利用。
比如下面這張圖:這樣求得的最小流爲200,而實際的可行最小流解只需100。
這裏寫圖片描述
所有我們先不加匯點到源點的流量爲正無窮的邊,跑一遍最大流。這是是將原圖中的環流滿。連上匯點到源點的流量爲正無窮的邊後再跑一次。若所有附加邊滿流,則第二次的答案即爲最小流,否則無解。

例題:sgu176

題目大意:有一個加工生產的機器,起點爲1終點爲n,中間生產環節有貨物加工數量限制,輸出u v z c,描述一個加工環節,當c等於1時表示這個加工的環節必須對紐帶上的貨物全部加工(即上下界都爲z),c等於0表示加工上界爲z,下界爲0,起點最少需要投放多少貨物才能傳送帶正常工作。

解題思路:按上面的方式構圖即可。

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<cmath>
#include<queue>
#include<vector>
using namespace std;

#define N 110
#define INF 1000000007
struct edge{
    int x,next,data,id; 
}e[N*N];
int tot=1,first[N],sum[N],c,dis[N],f[N*N],ans,n,m,x,y,z,s,t;
queue<int>q;

void add(int x,int y,int z,int id){
    e[++tot].x=y;
    e[tot].data=z;
    e[tot].id=id;
    e[tot].next=first[x];
    first[x]=tot;
}
bool bfs(){
    memset(dis,-1,sizeof(dis));
    q.push(s); dis[s]=1;
    while(!q.empty()){
        x=q.front(); q.pop();
        for(int i=first[x];i;i=e[i].next)
        if(e[i].data&&dis[e[i].x]==-1){
            dis[e[i].x]=dis[x]+1;
            q.push(e[i].x);
        }
    }
    return dis[t]!=-1;
}
int dfs(int x,int y){
    if(x==t||y==0) return y;
    int tmp,rec=0;
    for(int i=first[x];i;i=e[i].next)
    if(dis[e[i].x]==dis[x]+1&&e[i].data){
        tmp=dfs(e[i].x,min(y-rec,e[i].data));
        rec+=tmp; e[i].data-=tmp; e[i^1].data+=tmp;
        if(rec==y) return rec;
    }
    if(rec==0) dis[x]=-1;
    return rec;
}
int dinic(){
    int ans=0;
    while(bfs()) 
    ans+=dfs(s,INF);
    return ans;
}

void work(){
    for(int i=1;i<=m;i++){
        scanf("%d%d%d%d",&x,&y,&c,&z);
        if(z) sum[x]-=c,sum[y]+=c,f[i]=c;
        else add(x,y,c,i),add(y,x,0,i);
    }
    s=n+1; t=n+2;
    for(int i=1;i<=n;i++)
    if(sum[i]>0) add(s,i,sum[i],0),add(i,s,0,0);
    else add(i,t,-sum[i],0),add(t,i,0,0);
    dinic();
    add(n,1,INF,0); add(1,n,0,0);
    ans=dinic();
    for(int i=first[s];i;i=e[i].next)
    if(e[i].data){puts("Impossible");return;}
    printf("%d\n",ans);
    for(int i=3;i<=tot;i+=2) f[e[i].id]=e[i].data;
    for(int i=1;i<m;i++) printf("%d ",f[i]);
    printf("%d\n",f[m]);
}

int main(){
    while(~scanf("%d%d",&n,&m)){
        memset(sum,0,sizeof(sum));
        memset(first,0,sizeof(first));
        tot=1; work();
    }
    return 0;
}

4.上下界費用流

先認準是上面模型三種中的哪一種,按其構圖方法構圖。對於原圖中的邊,費用爲該邊的費用,對於附加邊,費用爲0。將最大流改爲費用流即可。但是答案還要加上所有邊的費用乘上該邊的流量下界。應爲我們算出來的費用是沒有計算下界的費用的。

例題:3876: [Ahoi2014]支線劇情

題目大意:一張有向無環圖,每條邊有費用。從任意點都可回到起點,無需費用。求由起點遍歷所有邊的最小費用。

解題思路:每條邊必須經過一次,便將邊的上界設爲正無窮,下界設爲1。任何點可以回到起點,便將所有點向起點連一條上界爲正無窮,下界爲0,費用爲0的邊。整個圖變成了一個無源匯的圖。按無源匯的可行流構圖,跑費用流就可以了。

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<cmath>
#include<queue>
#include<vector>
using namespace std;

#define N 400
#define INF 1000000007
int first[N],dis[N],c[N*N],pre[N],du[N];
int p,q,n,s,t,k,x,y,tot=1,sum;
bool v[N];
struct edge{
    int from,to,data,cost,next;
}e[N*N];

void add(int x,int y,int d,int c){
    e[++tot].to=y;
    e[tot].from=x;
    e[tot].data=d;
    e[tot].cost=c;
    e[tot].next=first[x];
    first[x]=tot;
}
bool spfa(){
    memset(dis,63,sizeof(dis));
    dis[c[1]=s]=0; v[s]=true;
    for(p=q=1;p<=q;v[c[p]]=false,p++)
     for(int i=first[c[p]];i;i=e[i].next)
     if(e[i].data&&dis[e[i].to]>dis[c[p]]+e[i].cost){
        dis[e[i].to]=dis[c[p]]+e[i].cost;
        pre[e[i].to]=i;
        if(!v[e[i].to]) v[c[++q]=e[i].to]=true;
     }
    return dis[t]!=dis[0];
}
int costflow(){
    int ans=0,flow;
    while(spfa()){
        flow=INF;
        for(int i=pre[t];i;i=pre[e[i].from])
        flow=min(flow,e[i].data);
        for(int i=pre[t];i;i=pre[e[i].from]){
            ans+=flow*e[i].cost;
            e[i].data-=flow; e[i^1].data+=flow;
        }
    }
    return ans;
}
int main(){
    scanf("%d",&n);
    s=n+1; t=n+2;
    for(int i=1;i<=n;i++){
        scanf("%d",&k);
        for(int j=1;j<=k;j++){
            scanf("%d%d",&x,&y);
            add(i,x,INF,y); add(x,i,0,-y);
            du[x]++; du[i]--;
            sum+=y;
        }
        if(i!=1) add(i,1,INF,0),add(1,i,0,0);
    }
    for(int i=1;i<=n;i++)
    if(du[i]>0) add(s,i,du[i],0),add(i,s,0,0);
    else add(i,t,-du[i],0),add(t,i,0,0);
    printf("%d\n",costflow()+sum);
    return 0;
}
發佈了60 篇原創文章 · 獲贊 8 · 訪問量 3萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章