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 7
6 12 no
4 8 yes
3 12 yes
5 9 no
6 11 yes
6 5 no
15 4 yes
16 15 yes
10 3 no
6 16 no
2 6 no
6 2 4
1 1 yes
2 3 yes
3 4 yes
4 5 no
5 6 yes
6 6 yes
3 3 2
1 2 no
3 4 yes
3 5 no

0 0 0

输出:

no
5
6
end
no

 */
/************************************************************************/
#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;
}
时间: 2024-10-05 18:54:45

POJ 1417 True Liars(并查集+DP)的相关文章

POJ 1417 True Liars 并查集+背包

题目链接:http://poj.org/problem?id=1417 解题思路:比较容易想到的是并查集,然后把第三组数据测试一下之后发现这并不是简单的并查集,而是需要合并之后然后判断的.并且鉴于题目要求输出数据,因此还要记录数据,可以说是非常有意思的题目. 首先,如果a b yes,那么a与b一定都是圣人或者恶人:反之,如果a b no,那么两者一个圣人,一个恶人.因此可以将所有元素分为若干个集合,每个集合中放两个小集合,表示两种不一样的人,当然并不知道到底哪个里面是圣人,恶人. 另一个比较棘

POJ 1417 True Liars

POJ连炸多日-- 1.发现a,b相同会回答yes 否则回答no 2.套用并查集模板 画图 设边权:相同为0,不同为1 猜\(fav[fa1]=fav[a] \) xor \( fav[b] \;\) xor \( d\),枚举各种情况果然成立-- 3.得到一棵树 其实中途会进行路径压缩-- 可以统计每棵树中与代表元素相同的个数(A[i])和不同的个数(B[i])(边权为1说明不同,为0说明相同) 然后问题就转换成 \(n = 树的棵数\) \(X[i] = A[i]\;\;\;or\;\;\;

poj1417(种类并查集+dp)

题目:http://poj.org/problem?id=1417 题意:输入三个数m, p, q 分别表示接下来的输入行数,天使数目,恶魔数目: 接下来m行输入形如x, y, ch,ch为yes表示x说y是天使,ch为no表示x说y不是天使(x, y为天使,恶魔的编号,1<=x,y<=p+q):天使只说真话,恶魔只说假话: 如果不能确定所有天使的编号,输出no,若能确定,输出所有天使的编号,并且以end结尾: 注意:可能会有连续两行一样的输入:还有,若x==y,x为天使: 思路:种类并查集+

POJ 2524 Ubiquitous Religions (幷查集)

Ubiquitous Religions Time Limit: 5000MS   Memory Limit: 65536K Total Submissions: 23090   Accepted: 11378 Description There are so many different religions in the world today that it is difficult to keep track of them all. You are interested in findi

[ACM] POJ 3295 Ubiquitous Religions (并查集)

Ubiquitous Religions Time Limit: 5000MS   Memory Limit: 65536K Total Submissions: 23093   Accepted: 11379 Description There are so many different religions in the world today that it is difficult to keep track of them all. You are interested in findi

poj 1456 Supermarket (贪心+并查集)

# include <stdio.h> # include <algorithm> # include <string.h> using namespace std; int fa[10010]; struct node { int p; int d; }; struct node a[10010]; bool cmp(node a1,node a2)//利润从大到小 { return a1.p>a2.p; } int find(int x) { if(fa[x]

[ACM] POJ 1611 The Suspects (并查集,输出第i个人所在集合的总人数)

The Suspects Time Limit: 1000MS   Memory Limit: 20000K Total Submissions: 21586   Accepted: 10456 Description Severe acute respiratory syndrome (SARS), an atypical pneumonia of unknown aetiology, was recognized as a global threat in mid-March 2003. T

poj 2513 Colored Sticks 并查集 字典树 欧拉回路判断

点击打开链接题目链接 Colored Sticks Time Limit: 5000MS   Memory Limit: 128000K Total Submissions: 30273   Accepted: 8002 Description You are given a bunch of wooden sticks. Each endpoint of each stick is colored with some color. Is it possible to align the sti

poj 1733 Parity game 并查集 离散化

点击打开链接题目链接 Parity game Time Limit: 1000MS   Memory Limit: 65536K Total Submissions: 6249   Accepted: 2413 Description Now and then you play the following game with your friend. Your friend writes down a sequence consisting of zeroes and ones. You cho