bzoj3894: 文理分科

传送门:http://www.lydsy.com/JudgeOnline/problem.php?id=3894

思路:令S为学文,T为学理

对于额外的收入,新加一个点

也可以用二元组建图来解决

还是这个图,x表示一个学生,与S连通表示学文,与T连通表示学理

y表示新加的点,用来计算同时学理的额外收益,与S连通表示不要额外收益,与T连通表示要额外收益

先算出总收益,在用最小割减去即可


解方程:

c+d=B[i]+WB//学生i学文,不要额外收益,损失学理收益,损失额外收益
a+b=A[i]//学生i学理,要额外收益,损失学文收益
b+e+c=inf//学生i学文,要额外收益,这不可能,损失为inf
a+f+d=A[i]+WB//学生i学理,不要额外收益,损失学文收益,损失额外收益

a=A[i]
b=0
c=B[i]
d=WB
e=inf
f=0

学文类似,但要注意一些边方向不同,收益也不同


所以最终建图就是:

S向每个学生连边,容量为学文的收益,每个学生向T连边,容量为学理的收益

S向每个代表相邻5个人都学文收益点连边,容量为同时学文的收益

该点再向对应5个人连边,容量为inf,表示只要有人学理,就必须割掉同时学文的收益

学理类似

对应5个人向该点连inf的边,该点向T连同时学理的收益的边


#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
const int maxn=30010,maxm=1000010,inf=1061109567;
const int dx[]={0,0,0,1,-1};
const int dy[]={0,1,-1,0,0};
using namespace std;
int n,m,sum;
int stu(int x,int y){return (x-1)*m+y;}
int art(int x,int y){return (x-1)*m+y+n*m;}
int sci(int x,int y){return (x-1)*m+y+n*m*2;}

struct Flow{
	int pre[maxm],now[maxn],son[maxm],val[maxm],tot,dis[maxn],q[maxn+10],head,tail,S,T;
	void add(int a,int b,int c){pre[++tot]=now[a],now[a]=tot,son[tot]=b,val[tot]=c;}
	void ins(int a,int b,int c){add(a,b,c),add(b,a,0);}
	void init(){memset(now,0,sizeof(now)),tot=1,S=0,T=3*n*m+1;}
	void build(){
		for (int i=1,x;i<=n;i++) for (int j=1;j<=m;j++) scanf("%d",&x),sum+=x,ins(S,stu(i,j),x);
		for (int i=1,x;i<=n;i++) for (int j=1;j<=m;j++) scanf("%d",&x),sum+=x,ins(stu(i,j),T,x);
		for (int i=1,x;i<=n;i++) for (int j=1;j<=m;j++){
			scanf("%d",&x),sum+=x;
			for (int k=0;k<5;k++){
				int nx=i+dx[k],ny=j+dy[k];
				if (nx<1||nx>n||ny<1||ny>m) continue;
				ins(art(i,j),stu(nx,ny),inf);
			}
			ins(S,art(i,j),x);
		}
		for (int i=1,x;i<=n;i++) for (int j=1;j<=m;j++){
			scanf("%d",&x),sum+=x;
			for (int k=0;k<5;k++){
				int nx=i+dx[k],ny=j+dy[k];
				if (nx<1||nx>n||ny<1||ny>m) continue;
				ins(stu(nx,ny),sci(i,j),inf);
			}
			ins(sci(i,j),T,x);
		}
	}
	bool bfs(){
		memset(dis,-1,sizeof(dis));
		q[tail=1]=S,dis[S]=head=0;
		while (head!=tail){
			if (++head>maxn) head=1;
			int x=q[head];
			for (int y=now[x];y;y=pre[y])
				if (val[y]&&dis[son[y]]==-1){
					if (++tail>maxn) tail=1;
					dis[son[y]]=dis[x]+1,q[tail]=son[y];
				}
		}
		return dis[T]>0;
	}
	int find(int x,int low){
		if (x==T) return low;
		int y,res=0;
		for (y=now[x];y;y=pre[y]){
			if (dis[son[y]]!=dis[x]+1||!val[y]) continue;
			int tmp=find(son[y],min(low,val[y]));
			res+=tmp,low-=tmp,val[y]-=tmp,val[y^1]+=tmp;
			if (!low) break;
		}
		if (!y) dis[x]=-1;
		return res;
	}
	void work(){
		while (bfs()) sum-=find(S,inf);
		printf("%d\n",sum);
	}
}F;

int main(){
	scanf("%d%d",&n,&m),F.init();
	F.build(),F.work();
	return 0;
}



posted @ 2016-01-21 14:21  orzpps  阅读(131)  评论(0编辑  收藏  举报