首页 > 代码库 > POJ 1417 True Liars(并查集+DP)

POJ 1417 True Liars(并查集+DP)

大意:

一个岛上有神与恶魔两个种族,神会说真话,恶魔会说假话。已知神与恶魔的个数,但不知道具体个人是属于哪个。

n,x,y

这个人问n次 ,x为神的个数,y为恶魔的个数。

每次的问题为 xi,yi,a 问xi ,yi是否为神? a为yes/no。注意xi,yi可能为同一个人

若最终可得出哪些是神则从小到大输出神的编号,并最终输出end

否则输出no


思路:

经过简单推理可得,只要是说yes,xi,yi为同一个族,no则不为同一个族。

这样通过使用并查集+relation(relation[i]为i与父亲的关系)可得出

但是分为了几个不同的群,判断是否可以拼成神的个数。

使用DP,即背包问题。这里的状态定义为dp[i][j]前i个群中拼成j有几种情况。

若情况惟一则正确,否则no


注意:



/************************************************************************/
/* 
提供测试数据:11 9 76 12 no4 8 yes3 12 yes5 9 no6 11 yes6 5 no15 4 yes16 15 yes10 3 no6 16 no2 6 no6 2 41 1 yes2 3 yes3 4 yes4 5 no5 6 yes6 6 yes3 3 21 2 no3 4 yes3 5 no0 0 0输出:no56endno */
/************************************************************************/
#include<math.h>
#include<stdio.h>  
#include<string.h>
#include<algorithm>
#define maxn 700
using namespace std;
int N,M,T,X,Y;
int ans;
int parent[maxn];
int relate[maxn];
char str[10];
int indata[2*maxn];
int dp[maxn][maxn];
int path[maxn][maxn];
int setname[maxn];
int find(int *parent,int k);
int backpack()
{
	int i,j,k,t,m;
	k=0;
	memset(dp,0,sizeof(dp));
	memset(path,0,sizeof(path));
	memset(indata,0,sizeof(indata));
	memset(setname,0,sizeof(setname));
	for(i=1;i<=X+Y;i++)
		find(parent,i);//再压缩一下路径
	for(i=1;i<=X+Y;i++)
		if(parent[i]==-1)
			setname[k++]=i;//得到各个集合,以根结点标识
	for(i=0;i<k;i++)
		for(j=indata[2*i]=1;j<=X+Y;j++)//<span style="color:#FF0000;">根与自身定为同类</span>
			if(parent[j]==setname[i])
				if(relate[j]==0)
					indata[2*i]++;//统计与自己同类的元素个数
				else
					indata[2*i+1]++;
	dp[0][indata[0]]++;path[0][indata[0]]=1;
	dp[0][indata[1]]++;path[0][indata[1]]=2;
	/*for(i=0;i<k;i++)
		printf("%d,%d\n",indata[i*2],indata[i*2+1]);*//*这里WA了好久,之前用无限背包的想法,dp设为一维数组。但是这里不同的是背包为两种重量而非之前选或不选。所以不能用。否则会计算重叠。这里要求是全部的集合都使用完后再判断(因为每个集合中都是与自己相反或相同的类别)。*/       for(i=1;i<k;i++)
		for(j=X;j>=0;j--)
		{

			if(j>=indata[2*i] && dp[i-1][j-indata[2*i]] )
				dp[i][j]+=dp[i-1][j-indata[2*i]],path[i][j]=1;
			if(j>=indata[2*i+1] && dp[i-1][j-indata[2*i+1]] )
				dp[i][j]+=dp[i-1][j-indata[2*i+1]],path[i][j]=2;
		}
	if(dp[k-1][X]!=1)
		return 0;
	ans=0;
	m=X;
	i=k-1;
	while(i>=0)
	{
		if(path[i][m]==1)
			t=0,m=m-indata[2*i];
		else
			t=1,m=m-indata[2*i+1];
		for(j=1;j<=X+Y;j++)
			if(parent[j]==setname[i] || j==setname[i])
				if(relate[j]==t)
					dp[0][ans++]=j;		
		i--;
	}
	sort(dp[0],dp[0]+ans);
	return 1;
		
		
}
void swap(int *a,int *b)
{
	int t;
	t=*a;
	*a=*b;
	*b=t;
}
int find(int *parent,int k)
{
	if(parent[k]==-1)
		return k;
	int t=parent[k];
	parent[k]=find(parent,parent[k]);
	relate[k]=(relate[k]+relate[t])%2;//并查集的高级应用,即每次合并都得到该点与父节点的关系。
	return parent[k];
}
int main()
{
	int i;
	int e,r,ee,rr,cnt;
	//freopen("input.txt","r",stdin);
	//freopen("output.txt","w+",stdout);
	while(scanf("%d%d%d",&N,&X,&Y))
	{
		if(X==0 && Y==0 && N==0)
			break;
		memset(parent,-1,sizeof(parent));
		memset(relate, 0,sizeof(relate));
		for(i=0;i<N;i++)
		{
			scanf("%d%d%s",&e,&r,str);
			if(r<e)swap(&r,&e);
			ee=find(parent,e);
			rr=find(parent,r);
			if(ee==rr)
				continue;//<span style="color:#FF0000;">若属于同一个集合则不合并,否则会有环路产生RE</span>
			if(str[0]=='y')
			{
				parent[rr]=ee,relate[rr]=(relate[e]+relate[r])%2;
			}
			else
				parent[rr]=ee,relate[rr]=(1+relate[r]+relate[e])%2;
		}
		if(X==Y)//若X,Y个数相等则没有可能
		{
			printf("no\n");
			continue;
		}
		if(backpack()==0)
			printf("no\n");
		else
		{
			for(i=0;i<ans;i++)
				printf("%d\n",dp[0][i]);
			printf("end\n");
		}
	} 
  return 0;  
}


POJ 1417 True Liars(并查集+DP)