一:概述
一般的網絡流模型中對邊只有上界的限制,及流過該邊的流量不超過上界。上下界網絡流的不同之處在於,每條邊還有一個下界,即流過該邊的流量不少於下界。其實一般的網絡流也可以看做下界爲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。將最大流改爲費用流即可。但是答案還要加上所有邊的費用乘上該邊的流量下界。應爲我們算出來的費用是沒有計算下界的費用的。
題目大意:一張有向無環圖,每條邊有費用。從任意點都可回到起點,無需費用。求由起點遍歷所有邊的最小費用。
解題思路:每條邊必須經過一次,便將邊的上界設爲正無窮,下界設爲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;
}