相对于一般的网络流,有上下界的网络流的某些边多出了流量下界的限制,如边u->v,上下界为high、low,如果有流经过这条边,这个流必须在[low,high]这个区间内。这类题目主要要求解决下面三个问题,“有源汇、无源汇的可行流”、“有源汇的最大流”、“有源汇的最小流”,注意这里所说的源汇是原网络中的源汇,分别记为s、t。
这类题目的难点在于下界的限制很难处理,我们将所有有下界限制的边中分离出“必须边”来单独做考虑,所谓“必须边”就是必须要满流的边,如上述的边,我们可以拆成两条边,第一条是“必须边”,流量上限为low,第二条是一般的边,流量上限为high-low。
怎样对必须边进行考虑呢?我们建立新的超级源汇,记为ss、tt,对于所有必须边再次进行拆边处理,如上述必须边u->v,应该拆成ss->v、u->tt两条流量上限都为low的边(这里很重要,应该要好好理解,我当时学的时候是不断画图来体会这种思想的)。
经过上述步骤,我们所需要的网络已经建立好了,依次来分析那三个问题。(这里所提到的所有变量均为上面所定义的)
1、对ss到tt求一次最大流,记为f1。(在有源汇的情况下,先使整个网络趋向必须边尽量满足的情况)
2、添加一条边t->s,流量上限为INF,这条边记为p。(构造无源汇网络)
3、对ss到tt再次求最大流,记为f2。(要判断可行,必须先构造无源汇网络流,因此要再次求最大流)
如果所有必须边都满流,证明存在可行解,原图的最小流为“流经<边p>的流量”(原图已构造成无源汇网络,对于s同样满足入流 == 出流,只有新添加的边流向s,而s的出流就是原图的最小流)。
题目链接: sgu194 Reactor Cooling
题目大意:给n个点,及m根pipe,每根pipe用来流躺液体的,单向的,每时每刻每根pipe流进来的物质要等于流出去的物质,要使得m条pipe组成一个循环体,里面流躺物质。并且满足每根pipe一定的流量限制,范围为[Li,Ri].即要满足每时刻流进来的不能超过Ri(最大流问题),同时最小不能低于Li。
建图模型: 以前写的最大流默认的下界为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之和),有可行解。
题目链接:zoj3229 Shoot the Bullet
题目大意:一个屌丝给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=(第一次流满下界的流+第二次能流通的自由流)。
题目链接: 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容量为无穷的边,这里求最小流很容易让人产生歧路,为什么呢?当所有边满足下界条件并且能量守恒时,这时候求得的最大流不就是最小流么。这样是错误了,我开始了在这揣测了良久。
下面来看个例子:
这类题目的难点在于下界的限制很难处理,我们将所有有下界限制的边中分离出“必须边”来单独做考虑,所谓“必须边”就是必须要满流的边,如上述的边,我们可以拆成两条边,第一条是“必须边”,流量上限为low,第二条是一般的边,流量上限为high-low。
怎样对必须边进行考虑呢?我们建立新的超级源汇,记为ss、tt,对于所有必须边再次进行拆边处理,如上述必须边u->v,应该拆成ss->v、u->tt两条流量上限都为low的边(这里很重要,应该要好好理解,我当时学的时候是不断画图来体会这种思想的)。
经过上述步骤,我们所需要的网络已经建立好了,依次来分析那三个问题。(这里所提到的所有变量均为上面所定义的)
一、有源汇、无源汇的可行流。
求可行流,其实就是问是否存在一个方案可以使所有必须边都满流。对于有源汇的网络,我们可以添加一条边t->s,流量上限为INF,这样就变成了无源汇的网络。对于无源汇的网络,只要对ss到tt求一次最大流,若所有必须边都满流,则有可行解,若要求打印方案,只需将非必须边中流过的流量加上流量下界(必须边已满流)。二、有源汇的最大流
这里的最大流,前提是使所有必须边满流,再要求从s到t的流量最大(注意,这里所求的最大流是原网络的最大流,而我们求ss到tt的最大流只是用于判断是否能使所有必须边满流)。首先判断所有必须边是否满流,这里和问题一中提到的方法一样,注意这里是有源汇的网络。然后直接对残留网络求一次从s到t的最大流,这个最大流就是最终答案。三、有源汇的最小流
和问题二相反,我们要使所有必须边满流的情况下,要求从s到t的流量最小。这个问题比上面的问题都要复杂,分三个步骤。1、对ss到tt求一次最大流,记为f1。(在有源汇的情况下,先使整个网络趋向必须边尽量满足的情况)
2、添加一条边t->s,流量上限为INF,这条边记为p。(构造无源汇网络)
3、对ss到tt再次求最大流,记为f2。(要判断可行,必须先构造无源汇网络流,因此要再次求最大流)
如果所有必须边都满流,证明存在可行解,原图的最小流为“流经<边p>的流量”(原图已构造成无源汇网络,对于s同样满足入流 == 出流,只有新添加的边流向s,而s的出流就是原图的最小流)。
这类题目的建模难度都很小,几乎可以一眼看出网络流模型,主要是构图、求解方面的问题,这里贴出我找到的仅有的6道题目的核心代码,所有求最大流都是用Dinic,这里为了尽量简洁略去Dinic的实现。
—————————————————————————————————————————————————————————————————————————————
一些题:
无源汇有上下界最大流
题目链接: sgu194 Reactor Cooling
题目大意:给n个点,及m根pipe,每根pipe用来流躺液体的,单向的,每时每刻每根pipe流进来的物质要等于流出去的物质,要使得m条pipe组成一个循环体,里面流躺物质。并且满足每根pipe一定的流量限制,范围为[Li,Ri].即要满足每时刻流进来的不能超过Ri(最大流问题),同时最小不能低于Li。
建图模型: 以前写的最大流默认的下界为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、有源汇有上下界的最大流
题目链接:zoj3229 Shoot the Bullet
题目大意:一个屌丝给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 }