【背包dp】团队分组UVA1627

今天上午很神奇地被两个dp用小错误卡了一上午。。。

题意:给出每个人是否认识另外的人(可能单向认识),分两组让每组中的人互相认识,使两组人数差最小并输出方案

把每对不是互相认识的人连边,代表他们不能分到一个组,这样的话连通分量可能有很多个,然而各个连通分量之间是互不影响的,所以分别二分图染色就可以,然后统计两种颜色在这个连通分量里分别有多少人。这样就把人分成了k个组,每个组中可以让染黑色的人去Team1,或去Team2,这样是有差别的,比如黑色比白色多2人,如果Team1选了黑色,那么就会比Team2又多两人,反之就会少两人,这像什么?背包!但是取值可能出现负数,不能作为数组下标,那都加上一个大数就好了。bool数组dp[i][j]代表选前i组能否取到j

然后美滋滋地写完,WA,vjudge上还不给数据,还不会手写SPJ,只能自己造数据人眼对拍及其心酸,然后我终于拍出来了!多组数据dp数组没memset...多组数据真的坑,省选day2t1学长花式演示没换行没memset等各种花式爆炸。。。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<algorithm>
using namespace std;
struct Group{
	int N0,N1;
	int val(){
		return abs(N0-N1);
	}
}T[200000];
int n,m1,h[2020],C[2200],N1,N0,pre[2010][2010],A[20020],T1[5020],T2[5020];
bool v[2010][200],dp[2010][2010],flag;
struct edge{
	int next,to;
	void Add(int Next,int To){
		next=Next; to=To;
	}
}q[200200];
void addedge(int x,int y){
	q[++m1].Add(h[x],y); h[x]=m1;
	q[++m1].Add(h[y],x); h[y]=m1;
}
void Dfs(int x,int c){
	int i,y;
	C[x]=c;
	if (c==0) N0++;
	else N1++;
	for (i=h[x];i;i=q[i].next){
		y=q[i].to;
		if (C[y]!=-1){
			if (C[y]!=1-c){
				flag=1;
				return;
			}
			continue;
		}
		Dfs(y,1-c);
		if (flag) return;
	}
}//二分图染色
void dfs(int x,int c){
	if (C[x]==c) T1[++T1[0]]=x;
	else T2[++T2[0]]=x;
	C[x]=-1;
	
	int i,y;
	for (i=h[x];i;i=q[i].next){
		y=q[i].to;
		if (C[y]!=-1)
			dfs(y,c);
	}
}
void Work(){
	scanf("%d",&n);
	m1=0; flag=0;
	memset(v,0,sizeof(v));
	memset(h,0,sizeof(h));
	memset(q,0,sizeof(q));
	memset(C,-1,sizeof(C));
	memset(pre,0,sizeof(pre));
	memset(dp,0,sizeof(dp));
	T1[0]=T2[0]=0;
	
	int i,x,j,k=0;
	for (i=1;i<=n;i++){
		while (cin>>x){
			if (!x) break;
			v[i][x]=1;
		}
	}
	for (i=1;i<=n;i++)
		for (j=i+1;j<=n;j++)
			if (!v[i][j]||!v[j][i])
				addedge(i,j);
	for (i=1;i<=n;i++)
		if (C[i]==-1){
			A[++k]=i; N0=N1=0;
			Dfs(i,0);
			if (flag){
				printf("No solution\n"); return;
			}
			T[k].N0=N0; T[k].N1=N1;
		}
	dp[0][500]=1;//把每个个下标都+500,避免出现负权下标
	for (i=1;i<=k;i++)
		for (j=350;j<=650;j++)
			if (dp[i-1][j]){
				dp[i][j+T[i].val()]=dp[i][j-T[i].val()]=1;
				pre[i][j+T[i].val()]=i,pre[i][j-T[i].val()]=-i;
			}
	for (i=500;i<=1000;i++)
		if (dp[k][i]){//dp[k][i]写成dp[n][i]了 
			N0=i; break;
		}
	//因为如果选一个集合能取到最优解,那么选它的补集结果是一样的,所以结果对称,只从一边找就可以 
	for (i=k;i>=1;i--)
		if (pre[i][N0]<0){
			if (T[i].N0>T[i].N1) dfs(A[i],1);
			else dfs(A[i],0);
			N0+=T[i].val();
		}
		else{
			if (T[i].N0>T[i].N1) dfs(A[i],0);
			else dfs(A[i],1);
			N0-=T[i].val();
		}
	for (i=0;i<=T1[0];i++) printf("%d ",T1[i]);
	printf("\n");
	for (i=0;i<=T2[0];i++) printf("%d ",T2[i]);
	printf("\n");
}
int main(){
	int T;
	scanf("%d",&T);
	for (int i=0;i<T;i++){
		if (i) printf("\n");
		Work();
	}
}

猜你喜欢

转载自blog.csdn.net/lerbon23james/article/details/79947807