[kuangbin带你飞]专题5 F - True Liars POJ - 1417

有一群好人和一群坏人,好人永远说真话,坏人永远说假话。现给出一组话,问能否唯一确定每个人是好人还是坏人。

Input

对于每组数据 第一行包括三个整数 n p q 表示话语的数量 好人和坏人的数量 接下来n行 形式如 x1 y1 yes/no 表示x1 说y1 是 好人/坏人 以0 0 0 结尾。 n<1000 ; p1 ,p2<300

Output

如果能唯一确定,输出所有的好人(每行一个,增序,以end结尾),否则输出no;

Sample Input

2 1 1
1 2 no
2 1 no
3 2 1
1 1 yes
2 2 yes
3 3 yes
2 2 1
1 2 yes
2 3 no
5 4 3
1 2 yes
1 3 no
4 5 yes
5 6 yes
6 7 no
0 0 0

Sample Output

no
no
1
2
end
3
4
5
6
end

先用并查集求出若干个大集合,大集合分2个不同种类人个数的小集合

从每个大集合中取1数,代表这个类型的人数。

如果最后能恰好取到q,且只有一种就行。

 

.。。。。

比较难的地方就是如何存标号,最后要升序输出 ,我们可以用map进行对应编号,最后再升序扫描输出就行了。

看看代码应该就差不多了

 

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<cstring>
#include<queue>
#include<string>
#include<map>
using namespace std;
const int M = 1000 +100;
int fa[M],d[M];
int judge[M];//dp完找路径时,选择的集合种类 
int get(int x)
{
	if(x==fa[x])return x;
	int root=get(fa[x]);
	d[x]=(d[x]+d[fa[x]])%2;
	return fa[x]=root;
}
int dp[M][M];
int se[M][3];//2个对立的集合。 
int main()
{
  	int n,p1,p2;
  	while(scanf("%d%d%d",&n,&p1,&p2))
	{
		if(n==0&&p1==0&&p2==0)break;
		memset(dp,0,sizeof(dp));
		memset(se,0,sizeof(se));
		map<int,int>ma;
		int x,y;
		char s[10];
		for(int i=1;i<=p1+p2;i++)
			fa[i]=i,d[i]=0;
		for(int i=1;i<=n;i++)
		{
			scanf("%d%d%s",&x,&y,s);
			int gx=get(x),gy=get(y);
			if(s[0]=='y')
			{
				if(gx!=gy)
				{
					fa[gx]=gy;
					d[gx]=(d[x]+d[y])%2;
				}
			}
			else
			{
				if(gx!=gy)
				{
					fa[gx]=gy;
					d[gx]=(1+d[x]+d[y])%2;
				}	
			}
		}
		int cnt=0;
		for(int i=1;i<=p1+p2;i++)
		{
			int gi=get(i);
			if(ma[gi]==0)ma[gi]=++cnt;//祖宗编号对应这第个集合 ,方便后面找到路径 
			se[ma[gi]][d[i]]++;
		//	if(size[gi]==1)
			//se[ma[gi]][!d[i]]++;
		}
	/*	for(int i=1;i<=cnt;i++)
		{
			printf("s1=%d   s2=%d\n",se[i][0],se[i][1]);
		}*/
		dp[0][0]=1;
		for(int i=1;i<=cnt;i++)
		{
			for(int j=0;j<=p1;j++)
			{
				if(j>=se[i][0])
				dp[i][j]+=dp[i-1][j-se[i][0]];
				if(j>=se[i][1])
				dp[i][j]+=dp[i-1][j-se[i][1]];
				//printf("i=%d  j=%d  dp[i][j]=%d\n",i,j,dp[i][j]);
			}
		}
		int j=p1;
	//	printf("  %d   +++++\n",dp[cnt][p1]);
		if(dp[cnt][p1]==1)
		{
			memset(judge,-1,sizeof(judge)); 
			for(int i=cnt;i>=1;i--)
			{
				if(dp[i-1][j-se[i][0]]==dp[i][j])
				{
					j-=se[i][0];
					judge[i]=0;
				}
				else if(dp[i-1][j-se[i][1]]==dp[i][j])
				{
					j-=se[i][1];
					judge[i]=1;//第i个集合选择1类的人 
				}
			}
			for(int i=1;i<=p1+p2;i++)
			{
				int gi=get(i);
				int loc=ma[gi];//i的祖宗对应的集合编号 
				if(judge[loc]==d[i])//判断是否选择这个集合  以及  要选择的类别与该点是否相同
				printf("%d\n",i);//从小到大输出保证了升序 
			}
			puts("end");
		}
		else
		puts("no");
	}
	return 0;
}

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值