有上下界的网络流问题

前几天就想写了的,一直没写,今天就写完吧。

因为在做这些上下界的题的时候,遇到了很多问题,在大神的帮助下还是一一解决了的。(英文没学好诶喂,,在sgu和poj各种wa。。)

主要是没看题,求上下界已经理解了的。。

分3种上下界网络流问题:(在本文只说做法和一些相关的东西,证明和推导请看后面写出的参考)

无源无汇的上下界最大流问题(没有源和汇我的理解,即流量只需满足流的3个性质即可,没有从源点流出从汇流进的约束):

做法:将图的下界分离到一个附加源和汇中,而上界则变为原弧的上界减去下界的差,构成一个附加网络,再在附加源汇上跑一次最大流即可。其中,记录每个点的入流下界和-出流下界和,当下界和>0时怎连入一条源s到这个点的边,上界为这个下界和,下界为0;当下界和<0则连入一条这个点到汇t的边,上界为下界和的绝对值,下界为0。

判断:仅当以源s为起点的弧或以汇t为终点的弧全部流满,则满足下界的可行流才存在,否则不存在。

例题:sgu194

代码:

#include <cstdio>

#include <cstring>

using namespace std;

#define CC(a, c) memset(a, c, sizeof(a))

#define FOR(i,a,n) for(i=a;i<=n;++i)



const int maxn=500, oo=~0u>>1, maxm=80810;

int S, T, cap[maxm], d[maxn], cur[maxn], gap[maxn], p[maxn];

int N, ihead[maxn], inext[maxm], to[maxm], from[maxm], cnt;

int dn[maxm], du[maxn];



int min(const int& a, const int& b) { return a < b ? a : b; }



int isap() {

	int flow=0, u, v, i, f=oo;

	CC(d, 0); CC(gap, 0);

	for(i=1; i<=N; ++i) cur[i]=ihead[i];

	u=S; gap[0]=N;

	while(d[S]<N) {

		for(i=cur[u]; i; i=inext[i]) if(cap[i] && d[u]==d[to[i]]+1) break;

		if(i) {

			cur[u]=i; v=to[i]; f=min(f, cap[i]); p[v]=i; u=v;

			if(v==T) {

				for(; v!=S; v=from[p[v]]) cap[p[v]]-=f, cap[p[v]^1]+=f;

				u=S; flow+=f; f=oo;

			}

		}

		else {

			if( !(--gap[d[u]]) ) break;

				d[u]=N;

				cur[u]=ihead[u];

				for(i=cur[u]; i; i=inext[i]) if(cap[i] && d[u]>d[to[i]]+1) d[u]=d[to[i]]+1;

				gap[d[u]]++; if(u!=S) u=from[p[u]];

			}

		}

	return flow;

}



void add(int u, int v, int c) {

	inext[++cnt]=ihead[u]; ihead[u]=cnt; from[cnt]=u; to[cnt]=v; cap[cnt]=c;

	inext[++cnt]=ihead[v]; ihead[v]=cnt; from[cnt]=v; to[cnt]=u; cap[cnt]=0;

}



void init(int s, int t, int n) {

	S=s; T=t; N=n; cnt=1;

	CC(ihead, 0); CC(du, 0); CC(dn, 0);

}



int main() {

	int n, m, u, v, down, up, i, flag;

	while(~scanf("%d%d", &n, &m)) {

		init(n+1, n+2, n+2);

		for(i=1; i<=m; ++i) {

			scanf("%d%d%d%d", &u, &v, &down, &up);

			add(u, v, up-down);

			du[u]-=down; du[v]+=down; //入流就+,出流就-

			dn[i]=down; //记录下界,因为最后这些边上的流量是不包括下界的,因此最后要加回来

		}

		for(i=1; i<=n; ++i) {

			if(du[i]>0) add(S,i,du[i]); //>0就从源s连一条弧

			if(du[i]<0) add(i,T,-du[i]); //<0就连一条弧到汇t

		}

		isap();

		flag=1;

		for(i=ihead[S]; i; i=inext[i]) if(cap[i]) { flag=0; break; } //仅当流满才存在

		if(!flag) puts("NO");

		else {

			puts("YES");

			for(i=1; i<=m; ++i)

			printf("%d\n",cap[(i<<1)+1]+dn[i]); //加回下界

		}

	}

	return 0;

}

 

有源汇的上下界最大流问题:

做法:从汇t连一条弧到源s,上界为无限大,下界为0。再设一个超级源ss和一个超级汇tt,按照无源汇上下界最大流的方法连边给ss和tt,从ss跑最大流到tt,判断是否有弧满后,去掉超级源和汇及其边,跑一次s-t的最大流(注意这里最大流绝不是t-s的反向弧,因为在第二次跑最大流的时候将这弧退掉了),最后根据题目要求输出即可。

技巧:我用的是链式前向星(邻接表),因此将ss和tt的head去掉即可,为什么呢,因为这不会影响到后面跑最大流,因为ss和tt本来就不在s-t的增广路上,但这里要注意,因为ss和tt还在图中,因此调用最大流的算法时,点的数量要包括了ss和tt。

例题:zoj3229

ps:这题是我调试了2天的。。太坑了,很多细节,还是注意看题吧。。比如说按输入的顺序输出。。导致我算法没错则一直在找算法的错误,还是请大神帮我找出来的。。。。。但是还是找到了一些bug且大神帮我优化了我的sap的一个地方,还是很感谢他的~

ps:这题样例是有问题的,请不要相信第一个case,用第二个case来做测试吧

代码:

#include <cstdio>

#include <cstring>

using namespace std;

#define CC(a, c) memset(a, c, sizeof(a))

#define FOR(i,a,n) for(i=a;i<=n;++i)



const int maxn=1510, oo=100000000, maxm=151000;

int cap[maxm], d[maxn], cur[maxn], gap[maxn], p[maxn];

int ihead[maxn], inext[maxm], to[maxm], from[maxm], cnt;

int inout[maxn], down[400][1200], id[400][1200]; //这里用maxn*maxn的话就mle了。。

int E[maxm][2], e;



int min(const int& a, const int& b) { return a < b ? a : b; }



int isap(int s, int t, int n) {

	int flow=0, i, u, f=oo;

	CC(gap, 0); CC(d, 0);

	FOR(i, 0, n) cur[i]=ihead[i];

	u=s; gap[0]=n;

	while(d[s]<n) {

		for(i=cur[u]; i; i=inext[i]) if(cap[i] && d[u]==d[to[i]]+1) break;

		if(i) {

			cur[u]=i; p[to[i]]=i; u=to[i];

			if(u==t) {

				for(f=oo; u!=s; u=from[p[u]]) f=min(f, cap[p[u]]); //这里的优化是大神教的

				for(u=t; u!=s; u=from[p[u]]) cap[p[u]]-=f, cap[p[u]^1]+=f;

				flow+=f;

			}

		}

		else {

			if( !(--gap[d[u]]) ) break;

			d[u]=n;

			cur[u]=ihead[u];

			for(i=cur[u]; i; i=inext[i]) if(cap[i] && d[u]>d[to[i]]+1)

				d[u]=d[to[i]]+1;

			gap[d[u]]++; if(u!=s) u=from[p[u]];

		}

	}

	return flow;

}



void add(int u, int v, int c) {

	inext[++cnt]=ihead[u]; ihead[u]=cnt; from[cnt]=u; to[cnt]=v; cap[cnt]=c;

	inext[++cnt]=ihead[v]; ihead[v]=cnt; from[cnt]=v; to[cnt]=u; cap[cnt]=0;

}



void init() {

	CC(ihead, 0); cnt=1; CC(down, 0); CC(id, 0); CC(inout, 0);

	e=0;

}



int main() {

	int n, m, i, g, d, t, l, r, sum, S, T;

	while(~scanf("%d%d", &n, &m)) {

		init();

		S=n+m+1; T=n+m+2;

		FOR(i, 1, m) {

			scanf("%d", &g);

			add(i+n, T, oo-g);

			inout[i+n]-=g; inout[T]+=g;

		}

		FOR(i, 1, n) {

			scanf("%d%d", &g, &d);

			add(S, i, d); 

			while(g--) {

				scanf("%d%d%d", &t, &l, &r);

				inout[i]-=l; inout[t+1+n]+=l;

				add(i, t+1+n, r-l);

				down[i][t+1]+=l; id[i][t+1]=cnt; //记录下标,反向弧,因为用的是残量网络跑的最大流,所以最后应该是反向弧

				E[++e][0]=i; E[e][1]=t+1; //记录顺序好输出

			}

		}

		add(T, S, oo); //添加汇到源的oo弧

		S=n+m+3; T=n+m+4; //设置超级源和汇

		sum=0;

		FOR(i, 1, n+m+2) {

			if(inout[i]>0) { sum+=inout[i]; add(S, i, inout[i]); } //sum是判断满流的一种方法

			if(inout[i]<0) add(i, T, -inout[i]);

		}

		if(isap(S, T, T)!=sum) printf("-1\n");

		else {

			ihead[S]=ihead[T]=0; //去掉超级源和汇的弧,注意,这里只是删掉点,也就是说,反向弧还存在。但是反向弧不会影响最大流,因为它不在增广路上面

			S=n+m+1; T=n+m+2; //恢复源和汇

			printf("%d\n", isap(S, T, T+2)); //打印最大流,这里要注意,因为点集里还有超级源和汇,所以N要包括那2个点

			FOR(i, 1, e)

				printf("%d\n", down[ E[i][0] ][ E[i][1] ]+cap[id[ E[i][0] ][ E[i][1]] ]);

		}

		printf("\n");

	} 

	return 0;

}

	

 

有源汇的上下界最小流问题:

做法:先不连边t-s,先构造了附加网络到超级源和超级汇中,然后跑超级源到超级汇的最大流,再连汇t到源s上界为无限大下界为0的弧,再跑一次超级源到超级汇的最大流即可,最小流就是t-s的反向弧。。。(ps,这里我不明白为什么这样做,问了之前帮我的大神,他也不知道,因此我先放下吧,以后问问其它大神。。其实我有一个理解,就是先将之前 上界-下界 的那些弧的最大跑出来, 然后连边后再跑,其实就是退流,将之前跑的退回去,这样既满足了下界限制,又是可行流,因为退流回去的是最大流,因此第二次跑出来的是最小流)

例题:sgu176

代码:

#include <cstdio>

#include <cstring>

using namespace std;

#define CC(a, c) memset(a, c, sizeof(a))

#define FOR(i,a,n) for(i=a;i<=n;++i)



const int maxn=1510, oo=100000000, maxm=151000;

int cap[maxm], d[maxn], cur[maxn], gap[maxn], p[maxn];

int ihead[maxn], inext[maxm], to[maxm], from[maxm], cnt;

int inout[maxn], ans[maxm], id[maxm];



int min(const int& a, const int& b) { return a < b ? a : b; }



int isap(int s, int t, int n) {

	int flow=0, i, u, f=oo;

	CC(gap, 0); CC(d, 0);

	FOR(i, 0, n) cur[i]=ihead[i];

	u=s; gap[0]=n;

	while(d[s]<n) {

		for(i=cur[u]; i; i=inext[i]) if(cap[i] && d[u]==d[to[i]]+1) break;

		if(i) {

			cur[u]=i; p[to[i]]=i; u=to[i];

			if(u==t) {

				for(f=oo; u!=s; u=from[p[u]]) f=min(f, cap[p[u]]);

				for(u=t; u!=s; u=from[p[u]]) cap[p[u]]-=f, cap[p[u]^1]+=f;

				flow+=f;

			}

		}

		else {

			if( !(--gap[d[u]]) ) break;

			d[u]=n;

			cur[u]=ihead[u];

			for(i=cur[u]; i; i=inext[i]) if(cap[i] && d[u]>d[to[i]]+1)

				d[u]=d[to[i]]+1;

			gap[d[u]]++; if(u!=s) u=from[p[u]];

		}

	}

	return flow;

}



void add(int u, int v, int c, int _id) {

	inext[++cnt]=ihead[u]; ihead[u]=cnt; from[cnt]=u; to[cnt]=v; cap[cnt]=c; id[cnt]=_id;

	inext[++cnt]=ihead[v]; ihead[v]=cnt; from[cnt]=v; to[cnt]=u; cap[cnt]=0; id[cnt]=0;

}



void init() {

	CC(ihead, 0); cnt=1; CC(ans, 0); CC(id, 0); CC(inout, 0);

}



int main() {

	int n, m, i, S, T, E, ss, tt;

	while(~scanf("%d%d", &n, &m)) {

		init();

		S=1; T=n;

		FOR(i, 1, m) {

			int u, v, c, k;

			scanf("%d%d%d%d", &u, &v, &c, &k);

			if(k) inout[u]-=c, inout[v]+=c, ans[i]=c;

			else add(u, v, c, i);

		}

		E=cnt;

		ss=n+1; tt=ss+1;

		FOR(i, 1, n) {

			if(inout[i]>0) add(ss, i, inout[i], 0);

			if(inout[i]<0) add(i, tt, -inout[i], 0);

		}

		isap(ss, tt, tt); //先跑一次最大流

		add(T, S, oo, 0); //连边

		isap(ss, tt, tt); //再跑一次最大流

		for(i=ihead[ss]; i; i=inext[i]) if(cap[i]) break;

		if(i) puts("Impossible");

		else {

			int res=0;

			for(i=ihead[T]; i; i=inext[i]) if(to[i]==S) break;

			res=cap[i^1];

			printf("%d\n", res);

			FOR(i, 2, E) ans[id[i]]=cap[i^1];

			FOR(i, 1, m) if(i!=m) printf("%d ", ans[i]);

				else printf("%d\n", ans[i]);

		}

	} 

	return 0;

}

	

 

 

参考:

周源《一种简易的方法求解流量有上下界的网络中网络流问题》

Mr. Ant博客:http://www.cnblogs.com/kane0526/archive/2013/04/05/3001108.html

你可能感兴趣的:(网络流)