此類問題可以分為三小類問題:
一、無源匯有上下界最大流
二、有源匯有上下界最大流
三、有源匯有上下界最小流
1、無源匯有上下界最大流
題目鏈接: sgu194 Reactor Cooling
題目大意:給n個點,及m根pipe,每根pipe用來流躺液體的,單向的,每時每刻每根pipe流進來的物質要等於流出去的物質,要使得m條pipe組成一個循環體,里面流躺物質。並且滿足每根pipe一定的流量限制,范圍為[Li,Ri].即要滿足每時刻流進來的不能超過Ri(最大流問題),同時最小不能低於Li。
解題思路:O(-1)。
建圖模型: 以前寫的最大流默認的下界為0,而這里的下界卻不為0,所以我們要進行再構造讓每條邊的下界為0,這樣做是為了方便處理。對於每根管子有一個上界容量up和一個下界容量low,我們讓這根管子的容量下界變為0,上界為up-low。可是這樣做了的話流量就不守恆了,為了再次滿足流量守恆,即每個節點"入流=出流”,我們增設一個超級源點st和一個超級終點sd。我們開設一個數組du[]來記錄每個節點的流量情況。
du[i]=in[i](i節點所有入流下界之和)-out[i](i節點所有出流下界之和)。
當du[i]大於0的時候,st到i連一條流量為du[i]的邊。
當du[i]小於0的時候,i到sd連一條流量為-du[i]的邊。
最后對(st,sd)求一次最大流即可,當所有附加邊全部滿流時(即maxflow==所有du[]>0之和),有可行解。

1 #include <iostream> 2 #include <cstdio> 3 #include <cmath> 4 #include <algorithm> 5 #include <cstring> 6 using namespace std; 7 8 const int mn=22222; 9 const int mm=1000000; 10 const int oo=0x3fffffff; 11 int node, st, sd, edge, Edge; 12 int reach[mm], flow[mm], next[mm]; 13 int head[mn], work[mn], dis[mn], que[mn]; 14 int du[mm], ans[mm], id[mm], dn[mm]; 15 16 inline void init(int _node, int _st, int _sd) 17 { 18 node=_node, st=_st, sd=_sd; 19 for(int i=0; i<node; i++) 20 head[i]=-1; 21 edge=0; 22 } 23 24 inline void addedge(int u, int v, int c1, int c2, int ID) 25 { 26 id[edge]=ID, reach[edge]=v, flow[edge]=c1, next[edge]=head[u],head[u]=edge++; 27 id[edge]=0, reach[edge]=u, flow[edge]=c2, next[edge]=head[v],head[v]=edge++; 28 } 29 30 bool bfs() 31 { 32 int u, v, l=0, h=0; 33 for(int i=0; i<node; i++) dis[i]=-1; 34 que[l++]=st; 35 dis[st]=0; 36 while(l!=h) 37 { 38 u=que[h++]; 39 if(h==mn) h=0; 40 for(int i=head[u]; i>=0; i=next[i]) 41 { 42 v=reach[i]; 43 if(flow[i]&&dis[v]<0) 44 { 45 dis[v]=dis[u]+1; 46 que[l++]=v; 47 if(l==mn) l=0; 48 if(v==sd) return true; 49 } 50 } 51 } 52 return false; 53 } 54 55 int dfs(int u, int exp) 56 { 57 if(u==sd) return exp; 58 for(int &i=work[u]; i>=0; i=next[i]) 59 { 60 int v=reach[i], tp; 61 if(flow[i]&&dis[v]==dis[u]+1&&(tp=dfs(v,min(flow[i],exp)))>0) 62 { 63 flow[i]-=tp; 64 flow[i^1]+=tp; 65 return tp; 66 } 67 } 68 return 0; 69 } 70 71 void Dinic() 72 { 73 while(bfs()) 74 { 75 for(int i=0; i<node; i++) work[i]=head[i]; 76 while(dfs(st,oo)); 77 } 78 } 79 80 int main() 81 { 82 int n,m; 83 while(~scanf("%d%d",&n,&m)) 84 { 85 init(n+2,0,n+1); 86 for(int i=1; i<=m; i++) 87 { 88 int u, v, down, up; 89 scanf("%d%d%d%d",&u,&v,&down,&up); 90 addedge(u,v,up-down,0,i); 91 du[u]-=down; 92 du[v]+=down; 93 dn[i]=down; 94 } 95 Edge=edge; 96 for(int i=1; i<=n; i++) 97 { 98 if(du[i]>0) addedge(st,i,du[i],0,0); 99 if(du[i]<0) addedge(i,sd,-du[i],0,0); 100 } 101 Dinic(); 102 bool flag=true; 103 for(int i=head[st]; i>=0; i=next[i]) 104 if(flow[i]>0) 105 { 106 flag=false; 107 break; 108 } 109 if(!flag) puts("NO"); 110 else 111 { 112 puts("YES"); 113 for(int i=0; i<Edge; i++) ans[id[i]]=flow[i^1]; 114 for(int i=1; i<=m; i++) 115 printf("%d\n",ans[i]+dn[i]); 116 } 117 } 118 return 0; 119 }
2、有源匯有上下界的最大流
題目大意:一個屌絲給m個女神拍照,計划拍照n天,每一天屌絲最多個C個女神拍照,每天拍照數不能超過D張,而且給每個女神i拍照有數量限制[Li,Ri],對於每個女神n天的拍照總和不能超過Gi,如果有解求屌絲最多能拍多少張照,並求每天給對應女神拍多少張照;否則輸出-1。
解題思路:增設一源點st,匯點sd,st到第i天連一條上界為Di下界為0的邊,每個女神到匯點連一條下界為Gi上界為oo的邊,對於每一天,當天到第i個女孩連一條[Li,Ri]的邊。
建圖模型:源點s,終點d。超級源點ss,超級終點dd。首先判斷是否存在滿足所有邊上下界的可行流,方法可以轉化成無源匯有上下界的可行流問題。怎么轉換呢?
增設一條從d到s沒有下界容量為無窮的邊,那么原圖就變成了一個無源匯的循環流圖。接下來的事情一樣,超級源點ss連i(du[i]>0),i連超級匯點(du[i]<0),
對(ss,dd)進行一次最大流,當maxflow等於所有(du[]>0)之和時,有可行流,否則沒有。
當有可行流時,刪除超級源點ss和超級終點dd,再對(s,d)進行一次最大流,此時得到的maxflow則為題目的解。為什么呢?因為第一次maxflow()只是求得所有滿足下界的流量,而殘留網絡(s,d)路上還有許多自由流(沒有和超級源點和超級匯點連接的邊)沒有流滿,所有最終得到的maxflow=(第一次流滿下界的流+第二次能流通的自由流)。

1 #include <iostream> 2 #include <cstdio> 3 #include <cstring> 4 #include <queue> 5 #include <algorithm> 6 using namespace std; 7 8 const int mn=2222; 9 const int mm=1000000; 10 const int oo=100000000; 11 int node, st, sd, edge; 12 int reach[mm], flow[mm], next[mm]; 13 int head[mn], work[mn], dis[mn], que[mn]; 14 int du[mn], dn[444][1111], id[444][1111]; 15 16 inline void init(int _node, int _st, int _sd) 17 { 18 node=_node, st=_st, sd=_sd; 19 for(int i=0; i<node; i++) 20 head[i]=-1, du[i]=0; 21 edge=0; 22 } 23 24 inline void addedge(int u, int v, int c1, int c2) 25 { 26 reach[edge]=v, flow[edge]=c1, next[edge]=head[u],head[u]=edge++; 27 reach[edge]=u, flow[edge]=c2, next[edge]=head[v],head[v]=edge++; 28 } 29 30 bool bfs() 31 { 32 int u, v, l=0, h=0; 33 for(int i=0; i<node; i++) dis[i]=-1; 34 que[l++]=st; 35 dis[st]=0; 36 while(l!=h) 37 { 38 u=que[h++]; 39 if(h==mn) h=0; 40 for(int i=head[u]; i>=0; i=next[i]) 41 { 42 v=reach[i]; 43 if(flow[i]&&dis[v]<0) 44 { 45 dis[v]=dis[u]+1; 46 que[l++]=v; 47 if(l==mn) l=0; 48 if(v==sd) return true; 49 } 50 } 51 } 52 return false; 53 } 54 55 int dfs(int u, int exp) 56 { 57 if(u==sd) return exp; 58 for(int &i=work[u]; i>=0; i=next[i]) 59 { 60 int v=reach[i], tp; 61 if(flow[i]&&dis[v]==dis[u]+1&&(tp=dfs(v,min(flow[i],exp)))>0) 62 { 63 flow[i]-=tp; 64 flow[i^1]+=tp; 65 return tp; 66 } 67 } 68 return 0; 69 } 70 71 int Dinic() 72 { 73 int max_flow=0, flow; 74 while(bfs()) 75 { 76 for(int i=0; i<node; i++) work[i]=head[i]; 77 while(flow=dfs(st,oo)) max_flow+=flow; 78 } 79 return max_flow; 80 } 81 82 int main() 83 { 84 int n, m, g, l, r, c, d; 85 while(scanf("%d%d",&n,&m)!=EOF) 86 { 87 init(n+m+2,n+m,n+m+1); 88 for(int i=0; i<m; i++) 89 { 90 scanf("%d",&g); 91 du[sd]+=g; 92 du[i+n]-=g; 93 addedge(i+n,sd,oo-g,0); 94 } 95 memset(id,0,sizeof(id)); 96 for(int i=0; i<n; i++) 97 { 98 scanf("%d%d",&c,&d); 99 addedge(st,i,d,0); 100 for(int j=0; j<c; j++) 101 { 102 scanf("%d%d%d",&g,&l,&r); 103 du[i]-=l; 104 du[g+n]+=l; 105 dn[i][g]=l; 106 addedge(i,g+n,r-l,0); 107 id[i][g]=edge-1; 108 } 109 } 110 addedge(sd,st,oo,0); 111 st=node, sd=node+1, node+=2; 112 head[st]=head[sd]=-1; ///!! 113 int sum=0; 114 for(int i=0; i<node-2; i++) 115 { 116 if(du[i]>0) sum+=du[i], addedge(st,i,du[i],0); 117 else addedge(i,sd,-du[i],0); 118 } 119 int maxflow=Dinic(); 120 if(maxflow!=sum) puts("-1"); 121 else 122 { 123 head[st]=head[sd]=-1, node-=2; 124 st=node-2, sd=node-1; 125 maxflow=Dinic(); 126 printf("%d\n",maxflow); 127 for(int i=0; i<n; i++) 128 { 129 for(int j=0; j<m; j++) 130 if(id[i][j]) 131 printf("%d\n",flow[id[i][j]]+dn[i][j]); 132 } 133 } 134 puts(""); 135 } 136 return 0; 137 }
3、有源匯有上下界的最小流
題目鏈接: sgu176 Flow construction
題目大意:有一個類似於工業加工生產的機器,起點為1終點為n,中間生產環節有貨物加工數量限制,輸出u v z c, 當c等於1時表示這個加工的環節必須對紐帶上的貨物全部加工(即上下界都為z),c等於0表示加工沒有上界限制,下界為0,求節點1(起點)最少需要投放多少貨物才能傳送帶正常工作。
解題思路:
1、du[i]表示i節點的入流之和與出流之和的差。
2、增設超級源點st和超級匯點sd,連(st,du[i](為正)),(-du[i](為負),sd)。 ///增設超級源點和超級匯點,因為網絡中規定不能有弧指向st,也不能有流量流出sd
3、做一次maxflow()。
4、源點(Sd)和起點(St)連一條容量為oo的邊。
5、再做一次maxflow()。
6、當且僅當所有附加弧滿載時有可行流,最后答案為flow[(Sd->St)^1],St到Sd最大流就是Sd到St最小流。
建圖模型:同樣轉換成先求無源匯有上下界的可行流,先添加一條d到s容量為無窮的邊,這里求最小流很容易讓人產生歧路,為什么呢?當所有邊滿足下界條件並且能量守恆時,這時候求得的最大流不就是最小流么。這樣是錯誤了,我開始了在這揣測了良久。
下面來看個例子:



這樣求得的最小流為200,而實際的可行最小流解只需100。
問題出在原圖中存在環(循環流),而我們沒有利用,導致流增大了。
解決方法:先不增加d->s容量為無窮的邊,進行一次maxflow(),如果還沒有滿流,則加一條(d,s)容量為無窮的邊,再進行一次maxflow(),當且僅當所有附加弧滿載時,有可行解,解為flow[(d->s)^1](即d到s的后悔邊權值)。

1 #include <iostream> 2 #include <cstdio> 3 #include <cmath> 4 #include <algorithm> 5 #include <cstring> 6 using namespace std; 7 8 const int mn=111; 9 const int mm=11111; 10 const int oo=0x3fffffff; 11 int node, st, sd, edge, St, Sd, Edge; 12 int reach[mm], flow[mm], next[mm]; 13 int head[mn], work[mn], dis[mn], que[mn]; 14 int du[mm], ans[mm], id[mm], dn[mm]; 15 16 inline void init(int _node, int _st, int _sd) 17 { 18 node=_node, st=_st, sd=_sd; 19 for(int i=0; i<node; i++) 20 head[i]=-1, du[i]=0; 21 edge=0; 22 } 23 24 inline void addedge(int u, int v, int c1, int c2, int ID) 25 { 26 id[edge]=ID, reach[edge]=v, flow[edge]=c1, next[edge]=head[u],head[u]=edge++; 27 id[edge]=0, reach[edge]=u, flow[edge]=c2, next[edge]=head[v],head[v]=edge++; 28 } 29 30 bool bfs() 31 { 32 int u, v, l=0, h=0; 33 for(int i=0; i<node; i++) dis[i]=-1; 34 que[l++]=st; 35 dis[st]=0; 36 while(l!=h) 37 { 38 u=que[h++]; 39 if(h==mn) h=0; 40 for(int i=head[u]; i>=0; i=next[i]) 41 { 42 v=reach[i]; 43 if(flow[i]&&dis[v]<0) 44 { 45 dis[v]=dis[u]+1; 46 que[l++]=v; 47 if(l==mn) l=0; 48 if(v==sd) return true; 49 } 50 } 51 } 52 return false; 53 } 54 55 int dfs(int u, int exp) 56 { 57 if(u==sd) return exp; 58 for(int &i=work[u]; i>=0; i=next[i]) 59 { 60 int v=reach[i], tp; 61 if(flow[i]&&dis[v]==dis[u]+1&&(tp=dfs(v,min(flow[i],exp)))>0) 62 { 63 flow[i]-=tp; 64 flow[i^1]+=tp; 65 return tp; 66 } 67 } 68 return 0; 69 } 70 71 void Dinic() 72 { 73 int max_flow=0, flow; 74 while(bfs()) 75 { 76 for(int i=0; i<node; i++) work[i]=head[i]; 77 while(flow=dfs(st,oo)) max_flow+=flow; 78 } 79 } 80 81 int main() 82 { 83 int n, m; 84 while(~scanf("%d%d",&n,&m)) 85 { 86 init(n+1,1,n); 87 for(int i=1; i<=m; i++) 88 { 89 int u, v, c, k; 90 scanf("%d%d%d%d",&u,&v,&c,&k); 91 if(k) du[u]-=c, du[v]+=c, ans[i]=c; 92 else addedge(u,v,c,0,i); 93 } 94 St=st, Sd=sd, Edge=edge; 95 st=node, sd=node+1, node+=2; ///增設超級源點和超級匯點,因為網絡中規定不能有弧指向st,也不能有流量流出sd 96 head[st]=head[sd]=-1; 97 for(int i=1; i<=n; i++) 98 { 99 if(du[i]>0) addedge(st,i,du[i],0,0); 100 if(du[i]<0) addedge(i,sd,-du[i],0,0); 101 } 102 Dinic(); 103 addedge(Sd,St,oo,0,0); 104 Dinic(); 105 bool flag=true; 106 for(int i=head[st]; i>=0; i=next[i]) 107 if(flow[i]>0) ///當且僅當附加弧達到滿負載有可行流 108 { 109 flag=false; 110 break; 111 } 112 if(!flag) 113 puts("Impossible"); 114 else 115 { 116 int res=0, i; 117 for(i=head[Sd]; i>=0; i=next[i]) 118 if(reach[i]==St) break; 119 res=flow[i^1]; 120 printf("%d\n",res); 121 for(i=0; i<Edge; i++) ans[id[i]]=flow[i^1]; 122 for(i=1; i<=m; i++) 123 { 124 if(i!=m) printf("%d ",ans[i]); 125 else printf("%d\n",ans[i]); 126 } 127 } 128 } 129 return 0; 130 }