上下界网络流

犹记得上一年的时候做过上下界网络流的题目,但是那个时候只会套网上的建图方法,对内部的原理一知半解,因此前几天碰到这个题的时候就有些凌乱了,于是,重新学习了下,深刻理解了,估计不会再忘记了,呵呵。

好了,言归正传。

上下界网络流的话这篇博客写的很不错的,介绍了怎么建图,怎么求解。然后这篇就是更加的数学化,理论化一点,不过也很好理解的额

嗯,然后就站在巨人的肩膀上,总结一下。

上下界网络流的问题严格的分,可以分为四类吧。

1:无源汇可行流  sgu 194

2:有源汇可行流  poj 2396  这题比较好,我建图建了将近200行

3:有源汇最大流  zoj 3496  这题比较劲爆,需要两次二分

4:有源汇最小流 hdu 3157   sgu 176

下面三种都是先转换成无源汇的来做,所以重点讲无源汇的网络流怎么做。上面两篇链接里的我就不再赘述了。

假设一条边的上界流为R 下界流为L,则 R-L为自由流,意思就是只要流了L,接下来要流多少随便。

无源汇的上下界可行流是指一种方案。流是循环在流的,每个点都满足流入等于流出。我们再细分一下就是sigma(进来的下界流) + sigma(进来的自由流) = sigma(出去的下界流)+sigma(出去的自由流)

假设我们要将这个网络流问题转换成普通的最大流问题,那么首先就是要把下界去掉,即下界为0。 去掉后要把下界的因素叠加在原图中,怎么叠加呢? 假设一个点进来的下界流总和减去出去的下界流总和为M,如果M为正,表示进来的自由流要比出去的自由流 少 M ,所以我们人为的向这个点补充进M(想想为什么)。如果M为负,也是类似的,从当前点额外地流出去M。 补充进流量和流出去需要建一个源点,一个汇点。

为什么这样子建好图求最大流后如果源点流出的边满流就有解?

想想看,如果源点流出的边都能满流,意味着什么??我们可以把中间的网络看成一个黑箱,源点是入口,汇点是出口,不管中间是循环的流也好,或者是直接经过一条链到达汇点也好,我们不关心,我们关心的只是进去的流能否流出来,其实也就是黑箱里的网络能否支持流进来的流。如果能够支持,说明原网络能够支持这么些必须要流的自由流。也就意味着原网络存在一个方案喽!


有源汇上下界的最大流的做法是:由汇点T向源点S建边(上界为INF,下界为0),(这一步非常犀利,等一下解释为啥么犀利),转换成无源汇的网络流问题,用上面的方法判断是否有解,如果有解的话,再跑一次源点S到汇点T的最大流,现在的最大流就是答案啦。。。

刚才那一步犀利犀利在只建了一条边就将原图转换成另一个已经可以解决的问题了(也许你不感觉犀利,但从无到有的想出这个方法在本菜看来实在犀利)

第一次跑完最大流的时候其实就可以求解网络中的一个可行流的流量了,这个流量其实就是最后加进去的那条边的反向流。因为加进去的时候下界是为0的,因此显然是正确的。


为什么最后再跑一遍最大流就能求得答案?

因为第一遍跑的是可行流,可能还有些边有余力可以再流一流,所以再跑一遍,原来那个可行流的流量记录在了T ->S的 反向流中,因次这个流再流一次就流回去了,所以再留一次就是答案!


好了,既然有下界,那肯定也可以求最小流。

先在原图中跑一遍最大流,然后连上T-> S (0,INF) 的边,再跑一遍

关于为什么第一遍跑的时候那个流量可能不是最小流,开头第一篇博客中已经有讲为什么了(环)

例题:poj 2396。

题意:有一个矩阵,告诉你每行的和以及每列的和,还有一些限制,综合起来其实就是告诉你某个数a[i][j]的范围,不过要自己去求。最后问你是否存在这样一个矩阵,有的话输出。

想仔细一点就可以看出这是个很裸的上下界流,但要先拆点,1~n*m表示矩阵中的点,n*m+1~到2*n*m表示拆成的点,2*n*m+1~2*n*m+m表示每一行,2*n*m+m+1~2*n*m+m+n表示每一列,那每行的和 每列的和可以通过新建源点,汇点来限制了,这些边的上下界都是一个定值,其余的就比较简单了,套个有源汇的上下界可行流就ok了。

#include<stdio.h>
#include<string.h>
#include<algorithm>
using namespace std;
const int MAX=10100;
const int INF=1000000000;
struct EDGE
{
	int v,c,next;
}edge[101000];
int E,head[MAX];
int gap[MAX],cur[MAX];
int pre[MAX],dis[MAX];
void add_edge(int s,int t,int c,int cc)
{
	edge[E].v=t; edge[E].c=c;
	edge[E].next=head[s];
	head[s]=E++;
	edge[E].v=s; edge[E].c=cc;
	edge[E].next=head[t];
	head[t]=E++;
}
int min(int a,int b){return (a==-1||b<a)?b:a;}
int SAP(int s,int t,int n)
{
	memset(gap,0,sizeof(gap));
	memset(dis,0,sizeof(dis));
	int i;
	for(i=0;i<n;i++)cur[i]=head[i];
	int u=pre[s]=s,maxflow=0,aug=-1,v;
	gap[0]=n;    
	while(dis[s]<n)
	{
loop:    for(i=cur[u];i!=-1;i=edge[i].next)
		 {
			 v=edge[i].v;
			 if(edge[i].c>0&&dis[u]==dis[v]+1)
			 {
				 aug=min(aug,edge[i].c);
				 pre[v]=u;
				 cur[u]=i;
				 u=v;
				 if(u==t)
				 {
					 for(u=pre[u];v!=s;v=u,u=pre[u])
					 {
						 edge[cur[u]].c-=aug;
						 edge[cur[u]^1].c+=aug;
					 }
					 maxflow+=aug;
					 aug=-1;
				 }
				 goto loop;
			 }
		 }
		 int mindis=n;
		 for(i=head[u];i!=-1;i=edge[i].next)
		 {
			 v=edge[i].v;
			 if(edge[i].c>0&&dis[v]<mindis)
			 {
				 cur[u]=i;
				 mindis=dis[v];
			 }
		 }
		 if((--gap[dis[u]])==0)break;
		 gap[dis[u]=mindis+1]++;
		 u=pre[u];
	}
	return maxflow;
}
int m , n , S, T;
int sum_row[210] , sum_col[210];
int low[210][210] , up[210][210] ,in[10010];
bool judge(int i,int j,char op,int c)
{
	if(op=='=')
	{
		if(c >= low[i][j] && c <= up[i][j])
		{
			low[i][j] = c;
			up[i][j]  = c;
		}
		else return false;
	}
	else if(op=='>')
	{
		low[i][j] = max(low[i][j],c+1);
		if(low[i][j] > up[i][j]) return false;
	}
	else 
	{
		up[i][j] = min(up[i][j],c-1);
		if(low[i][j] > up[i][j]) return false;
	}
	return true;
}
bool init()
{
	E=0;
	memset(head,-1,sizeof(head));
	memset(in,0,sizeof(in));
	int i , j , k , a, b , c;
	scanf("%d%d",&m,&n);
	int tot = 2 * n * m; 
	S = 0; T = tot + n + m + 1;
	for(i = 1; i <= m; i++)
	{
		scanf("%d",&sum_row[i]);
		in[i+tot] = sum_row[i];
		in[S] -= sum_row[i];
		//add_edge(S,i+tot,sum_row[i],0);
	}
	for(i = 1; i <= n; i++)
	{
		scanf("%d",&sum_col[i]);
		in[T] += sum_col[i];
		in[m+tot+i] = - sum_col[i];
		//add_edge(m+tot+i,T,sum_col[i],0);
	}
	add_edge(T,S,INF,0);
	scanf("%d",&k); char op[5];
	for(i = 1; i <= m; i++) for(j = 1; j <= n; j++) low[i][j] = 0, up[i][j] = 200000;
	bool flag = true;
	for(int x = 0; x < k; x++)
	{
		scanf("%d%d%s%d",&a,&b,op,&c);
		if(!a)
		{
			if(!b)
			{
				for(i = 1; i <= m; i++)
				{
					for(j = 1; j <= n; j++)
					{
						if(!judge(i,j,op[0],c)) flag = false;
					}
				}
			}
			else 
			{
				for(i = 1; i <= m; i++)
				{
					if(!judge(i,b,op[0],c)) flag = false;
				}
			}
		}
		else 
		{
			if(!b)
			{
				for(i = 1; i <= n; i++)
				{
					if(!judge(a,i,op[0],c)) flag = false;
				}
			}
			else 
			{
				if(!judge(a,b,op[0],c)) flag = false;
			}
		}
	}
/*	for(i = 1; i <= m; i++)
	{
		for(j = 1; j <= n; j++)
		{
			printf("i = %d j = %d  %d %d \n",i , j , low[i][j],up[i][j] );
		}
	}
*/
	return flag;
}
int ID(int i,int j)
{
	return (i-1)*n+j;
}
int ans[250][250];
bool check()
{
	for(int i = 1; i <= m; i++)
	{
		int s = 0;
		for(int  j =1; j <=n;j ++)
		{
			s += ans[i][j];
		}
		if(s!=sum_row[i]) return false;
	}
	for(int i = 1; i <= n; i++)
	{
		int s =  0;
		for(int j = 1; j <= m; j++)
		{
			s += ans[j][i];
		}
		if(s!=sum_col[i]) return false;
	}
	return true;
}
bool solve()
{
	int i , j , k;
	for(i = 1; i <= m; i++)
	{
		for(j = 1; j <= n; j++)
		{
			int c = up[i][j] - low[i][j];
			int id = ID(i,j);
		//	printf("i=%d j=%d  id = %d %d c = %d\n",i,j,id,id+n*m,c);
			add_edge(id,id+n*m,c,0);
			in[id] -= low[i][j];
			in[id+n*m] += low[i][j];
		}
	}
	int ss = T + 1, tt = ss + 1;
	for(i = 1; i <= m; i++)
	{
		for(j = 1; j <= n; j++)
		{
			add_edge(2*n*m+i,ID(i,j),200000,0);
		}
	}
	for(i = 1; i <= n; i++)
	{
		for(j = 1; j <= m; j++)
		{	//printf("%d %d\n",2*n*m+m+i,ID(j,i)+n*m);
			add_edge(ID(j,i)+m*n,2*m*n+m+i,200000,0);
		}
	}
	for(i = 0; i <= 2*m*n+m+n+1 ; i++)
	{
		//printf("in[%d]=%d\n",i,in[i]);
		if(in[i] > 0) add_edge(ss,i,in[i],0);
		if(in[i] < 0) add_edge(i,tt,-in[i],0);
	}
	SAP(ss,tt,tt+1);
	for(i=head[ss];i!=-1;i=edge[i].next)
	{
		if(edge[i].c) return false;
	}
	for(i = 1; i <= m; i++)
	{
		for(j = 1; j <= n; j++)
		{
			int id = ID(i,j);
			for(k = head[id]; k!=-1; k=edge[k].next)
			{
				if(edge[k].v <= 2*n*m)
				{
					ans[i][j] = low[i][j] + edge[k^1].c;
				}
			}
		}
	}
	for(i = 1; i <= m; i++)
	{
		printf("%d",ans[i][1]);
		for(j = 2; j <= n; j++)
		{
			printf(" %d",ans[i][j]);
		}
		puts("");
	}
	
/*	if(check()) {
		puts("YES");
	} else puts("NO");*/
	return true;
}
int main()
{
	int t;
//	freopen("budget.in","r",stdin);
//	freopen("budget.out","w",stdout);
	scanf("%d",&t);
	for(int i = 0; i < t; i++)
	{
		if(i) puts("");
		if(!init())
		{
			puts("IMPOSSIBLE");
		}
		else 
		{
			if(!solve()) puts("IMPOSSIBLE");
		}
	}
	return 0;
}

zoj 那题很好想,需要两次二分,但不是太好写,附上代码吧。

http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemCode=3496
#include<stdio.h>
#include<string.h>
typedef long long lld;
const int MAX=510;
const int INF=100000000;
struct EDGE
{
	int v,c,next;
}edge[1000000];
int E,head[MAX];
int gap[MAX],cur[MAX];
int pre[MAX],dis[MAX];
void add_edge(int s,int t,int c,int cc)
{
	edge[E].v=t; edge[E].c=c;
	edge[E].next=head[s];
	head[s]=E++;
	edge[E].v=s; edge[E].c=cc;
	edge[E].next=head[t];
	head[t]=E++;
}
int min(int a,int b){return (a==-1||b<a)?b:a;}
int SAP(int s,int t,int n)
{
	memset(gap,0,sizeof(gap));
	memset(dis,0,sizeof(dis));
	int i;
	for(i=0;i<n;i++)cur[i]=head[i];
	int u=pre[s]=s,aug=-1,v;
	int maxflow = 0;
	gap[0]=n;    
	while(dis[s]<n)
	{
loop:    for(i=cur[u];i!=-1;i=edge[i].next)
		 {
			 v=edge[i].v;
			 if(edge[i].c>0&&dis[u]==dis[v]+1)
			 {
				 aug=min(aug,edge[i].c);
				 pre[v]=u;
				 cur[u]=i;
				 u=v;
				 if(u==t)
				 {
					 for(u=pre[u];v!=s;v=u,u=pre[u])
					 {
						 edge[cur[u]].c-=aug;
						 edge[cur[u]^1].c+=aug;
					 }
					 maxflow+=aug;
					 aug=-1;
				 }
				 goto loop;
			 }
		 }
		 int mindis=n;
		 for(i=head[u];i!=-1;i=edge[i].next)
		 {
			 v=edge[i].v;
			 if(edge[i].c>0&&dis[v]<mindis)
			 {
				 cur[u]=i;
				 mindis=dis[v];
			 }
		 }
		 if((--gap[dis[u]])==0)break;
		 gap[dis[u]=mindis+1]++;
		 u=pre[u];
	}
	return maxflow;
}
int n , m , S , T , P  , ans1 , ans2 , maxc , minc;
int max_flow;
int u[10010] , v[10010] , c[10010];
int in[510];
void init()
{
	E = 0;
	memset(head,-1,sizeof(head[0])*600);
}
void Map_Init(int mid) // 流量上界为mid ,每条边的上界就是mid与c的最小值
{
	init();
	for(int i = 0; i < m; i++)
	{
		add_edge(u[i],v[i],min(mid,c[i]),0);
	}
}
void question1() // 二分上界
{
	int l = 0 , r = maxc;
	while(l <= r) 
	{
		int mid = l + r >> 1;
		Map_Init(mid);
		int tmp = SAP(S,T,n);
		if(tmp == max_flow)
		{
			ans1 = mid;
			r = mid - 1;
		}
		else 
		{
			l = mid + 1;
		}
	}
}
bool map_init(int mid)//mid 为下界
{
	init();
	memset(in,0,sizeof(in));
	for(int i = 0; i < m; i++)
	{
		if(c[i] < mid) return false;
		add_edge(u[i],v[i],c[i]-mid,0);
		in[v[i]] += mid;
		in[u[i]] -= mid;
	}
	return true;
}
bool  ok(int mid)
{
	int i , j;
	if(!map_init(mid)) return false;
	int ss = n , tt = n + 1;
	for(i = 0; i < n ; i++)
	{
		if(in[i] > 0) add_edge(ss,i,in[i],0);
		if(in[i] < 0) add_edge(i,tt,-in[i],0);
	}	
	add_edge(T,S,INF,0);
	SAP(ss,tt,tt+1);
	for(i = head[ss];i!=-1;i=edge[i].next)
	{
		if(edge[i].c) return false;
	}
	return SAP(S,T,tt+1) == max_flow;
}
void question2() // 二分下界
{
	int l = 0 , r = maxc ;// 理论上讲这个r赋为minc是可以的,但是WA了(因为如果大于minc就一定会有边的上界小于下界)
	while(l <= r)
	{
		int mid = l + r >> 1;
		if(ok(mid))
		{
			ans2 = mid;
			l = mid + 1;
		}
		else 
		{
			r = mid - 1;
		}
	}
}
int main()
{
	int t,i,j,k;
	scanf("%d",&t);
	while(t--) 
	{
		scanf("%d%d%d%d%d",&n,&m,&S,&T,&P);
		init();
		maxc = 0;
		minc = INF;
		for(i=0;i<m;i++)
		{
			scanf("%d%d%d",&u[i],&v[i],&c[i]);
			add_edge(u[i],v[i],c[i],0);
			if(c[i] > maxc) maxc = c[i];
			if(c[i] < minc) minc = c[i];
		}
		max_flow = SAP(S,T,n);
		question1();
		question2();
		printf("%lld %lld\n",(lld)ans1*P,(lld)ans2*P);
	}
	return 0;
}
/*
100
4 3 0 3 3
0 1 2
1 2 3
2 3 4

4 5 0 3 2
0 1 2
0 2 1
1 2 1
1 3 1
2 3 2

4 5 0 3 2
0 1 1
0 2 1
1 2 2
1 3 2
2 3 1

5 5 0 4 1
0 3 1
0 2 1
3 1 1
2 1 1
1 4 1

6 6
4 2
2 0
1 0
*/


最后再总结一下建图方法,方便现场赛可以快速的回忆起来。

1:无源汇的可行流 : 新建源点,汇点,M[i]为每个点进来的下界流减去出去的下界流,如果M[i]为正,由源点向改点建M[i]的边,反之,由该点向汇点建M[i]的边,原图中的边为每条边的上界建去下界。跑一遍最大流,每条边的流量加上下界流就是答案。

2:有源汇的最大流: 从汇点向源点建一条容量为INF的边,用上面的方法判断是否有解,有解就再跑一遍从原图中源点到汇点的最大流

3:有源汇的最小流:先跑一遍最大流,然后连上从汇点到源点的边,再按照1的方法做就好了

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值