0
点赞
收藏
分享

微信扫一扫

True Liars (POJ - 1417)带权并查集+dp路径

Just_Esme 2022-01-17 阅读 23

题目:

After having drifted about in a small boat for a couple of days, Akira Crusoe Maeda was finally cast ashore on a foggy island. Though he was exhausted and despaired, he was still fortunate to remember a legend of the foggy island, which he had heard from patriarchs in his childhood. This must be the island in the legend. In the legend, two tribes have inhabited the island, one is divine and the other is devilish, once members of the divine tribe bless you, your future is bright and promising, and your soul will eventually go to Heaven, in contrast, once members of the devilish tribe curse you, your future is bleak and hopeless, and your soul will eventually fall down to Hell. 

In order to prevent the worst-case scenario, Akira should distinguish the devilish from the divine. But how? They looked exactly alike and he could not distinguish one from the other solely by their appearances. He still had his last hope, however. The members of the divine tribe are truth-tellers, that is, they always tell the truth and those of the devilish tribe are liars, that is, they always tell a lie. 

He asked some of them whether or not some are divine. They knew one another very much and always responded to him "faithfully" according to their individual natures (i.e., they always tell the truth or always a lie). He did not dare to ask any other forms of questions, since the legend says that a devilish member would curse a person forever when he did not like the question. He had another piece of useful informationf the legend tells the populations of both tribes. These numbers in the legend are trustworthy since everyone living on this island is immortal and none have ever been born at least these millennia. 

You are a good computer programmer and so requested to help Akira by writing a program that classifies the inhabitants according to their answers to his inquiries. 

Input

The input consists of multiple data sets, each in the following format : 

n p1 p2 
xl yl a1 
x2 y2 a2 
... 
xi yi ai 
... 
xn yn an 

The first line has three non-negative integers n, p1, and p2. n is the number of questions Akira asked. pl and p2 are the populations of the divine and devilish tribes, respectively, in the legend. Each of the following n lines has two integers xi, yi and one word ai. xi and yi are the identification numbers of inhabitants, each of which is between 1 and p1 + p2, inclusive. ai is either yes, if the inhabitant xi said that the inhabitant yi was a member of the divine tribe, or no, otherwise. Note that xi and yi can be the same number since "are you a member of the divine tribe?" is a valid question. Note also that two lines may have the same x's and y's since Akira was very upset and might have asked the same question to the same one more than once. 

You may assume that n is less than 1000 and that p1 and p2 are less than 300. A line with three zeros, i.e., 0 0 0, represents the end of the input. You can assume that each data set is consistent and no contradictory answers are included. 
 

Output

For each data set, if it includes sufficient information to classify all the inhabitants, print the identification numbers of all the divine ones in ascending order, one in a line. In addition, following the output numbers, print end in a line. Otherwise, i.e., if a given data set does not include sufficient information to identify all the divine members, print no in a line.

中文题意:一个村庄有两类人,好人坏人, 好人总是说真话, 坏人总是说假话, 给你n个询问和好人、坏人的数量p q, 每个询问 x y yes/no, 表示 x 说 y 是 好/坏人。问是否能够唯一确定哪些是好人, 哪些是坏人, 如果可以输出好人的序号以"end"结尾, 否则输出"no"

分析:我们简单地分析能够发现,当一个人说另一个人是好人的时候有两种情况,第一种情况是第一个人是好人,那么第二个人也是好人,第二种情况就是第一个人是坏人,那么他说的是假话,则第二个人也是坏人,不管怎样,他们总是属于同一类人。同样的,当一个人说另一个人是坏人的时候也会分两种情况,第一种情况是第一个人是好人,那么第二个人就是坏人,如果第一个人是坏人,由于他说的是假话,那么第二个人就是好人,他们总是属于不同类别的人。知道这个结论我们就可以对题目中所给的条件进行分类了,这个显然是用并查集。但是大家有没有发现一个问题?就是无论是上述哪种情况,我们都无法直接确定哪一类人是好人,哪一类人是坏人。但最起码我们可以知道他们属于同一类还是不同类,那我们如何唯一确定哪些是好人呢?举个简单点的例子来说吧,我们现在有两个集合,一个集合中包含4,6(代表这个集合中其中4个人是一类,另外6个人是一类),另一个集合中有7和9,显然每个集合中都会有一类是好人,一类是坏人,现在告诉我们有15个人是好人,我们能知道哪些人是好人吗?显然只能是第一个集合的6人和第二个集合的9人是好人,而假如好人有13个呢?这个时候就会有两种情况,第一种情况是第一个集合中的6人和第二个集合中的7人是好人,第二种情况就是第一个集合中的4人和第二个集合中的9人是好人,所以这个时候就不能确定谁是好人谁是坏人。而这个问题抽象成数学问题就是,假如我们有n个好人,m个集合,我们必须从每个集合中的两个数中挑选一个,看最后能够组成n的情况数是否唯一,而这个问题我们可以类似于背包问题去解决它,其中dp[i][j]表示从前i个集合中挑选j个好人的情况数。

这道题目的大致思路就是这样,下面我来说一下具体的一些细节:

第一个就是我们如何在一个集合中存储两类人,这个就是一个带权并查集的操作了,并查集大家肯定都不陌生,它可以帮我们把有关联的人放一块,每个集合中都会有一个代表元素,我们可以假设与根节点的距离为奇数的节点代表与根节点不同类的人,而与根节点之间的距离为偶数的点来表示与根节点同一类的人,这样我们就可以在一个集合中存储两类人,这部分具体的实现代码如下:

int find(int x)
{
	int tmp=fu[x];
	if(x==tmp) return x;
	fu[x]=find(tmp);
	d[x]=(d[x]+d[tmp])%2;//更新距离,利用其直接父类进行更新 
	return fu[x];
}
void unite(int x,int y,int z)
{
	int f1=find(x),f2=find(y);
	if(f1!=f2)
	{
		fu[f2]=f1;
		d[f2]=(d[x]+d[y]+z)%2;//f2到f1的距离是由x和y的距离以及y和f2的关系以及x和f1的关系决定的 
	}
}

带权并查集的查找过程我就不作赘述了,就是每个节点岛根节点的距离都利用其父节点到根节点的距离来进行更新,我主要说一下两个集合合并的时候应该怎样处理,看这个到吗就可以看出,我显然是要让f1做合并后的集合的代表元素,那么fu[f2]=f1比较容易理解,关键是                          d[f2]=(d[x]+d[y]+z)%2这条语句的理解,d[f2]表示待合并的根节点到合并后的集合的根节点之间的距离,f2到f1的距离是由x和y的距离以及y和f2的关系以及x和f1的关系决定的

 如图所示:

如果x到y的距离是奇数(代表x和y不同类),也就是z=a+b+h为奇数,那么h=z-a-b,由于我们dp中存的是奇数还是偶数,z+a+b=h+2*(a+b),两者奇偶性相同,为了使得数组里面是正数,我们就用z+a+b代替h,这样就解决了带权并查集的问题。

剩下的就是路径输出问题了,我们知道,每次必然要从一个集合中选出一类作为好人,我们在dp的过程中就可以记录路径,这样最后我们直接在倒序遍历过程中存入答案即可。不要忘记对答案进行排序。

下面是代码:

#include<cstdio>
#include<iostream>
#include<cstring>
#include<vector>
#include<algorithm>
using namespace std;
const int N=1003;
int fu[N],d[N];//d[i]=0/1,表示第i个节点与其根节点(是/不是)一类人 
int cnt[N][2];//cnt[i][0/1]表示与第i个节点属于(同一类/不同类)的个数 
vector<int> s[N][2];//s[i][0/1]存储与第i个节点属于同一类/不同类的点 
int dp[N][N];//dp[i][j]记录前i个集合中有j个好人的方案数 
int pre[N][N];//记录背包中选取的物品 
bool vis[N];//记录是否出现在集合中 
int find(int x)
{
	int tmp=fu[x];
	if(x==tmp) return x;
	fu[x]=find(tmp);
	d[x]=(d[x]+d[tmp])%2;//更新距离,利用其直接父类进行更新 
	return fu[x];
}
void unite(int x,int y,int z)
{
	int f1=find(x),f2=find(y);
	if(f1!=f2)
	{
		fu[f2]=f1;
		d[f2]=(d[x]+d[y]+z)%2;//f2到f1的距离是由x和y的距离以及y和f2的关系以及x和f1的关系决定的 
	}
}
int main()
{
	int n,p,q;
	while(scanf("%d%d%d",&n,&p,&q)&&(n!=0||p!=0||q!=0))
	{
		for(int i=1;i<=p+q;i++)//初始化 
		{
			fu[i]=i;
			d[i]=cnt[i][0]=cnt[i][1]=0;
			vis[i]=false;
			s[i][0].clear();
			s[i][1].clear();
		}
		int x,y;
		char o[5];
		for(int i=1;i<=n;i++)
		{
			scanf("%d%d%s",&x,&y,o);
			if(o[0]=='y') unite(x,y,0);//yes代表x和y是一类人 
			else unite(x,y,1);//no代表x和y不是一类人
		}
        int count=0;//记录集合的个数(每个集合中有两个小集合,一个是好人,一个是坏人) 
		for(int i=1;i<=p+q;i++)
		{
			if(!vis[i])
			{
				int t=find(i);
				for(int j=i;j<=p+q;j++)
				{
					if(find(j)==t)
					{
						vis[j]=true;
						s[count+1][d[j]].push_back(j);
						cnt[count+1][d[j]]++;
					}
				}
				count++;
			}
		}
		memset(dp,0,sizeof(dp));
        dp[0][0]=1;
        for(int i=1;i<=count;i++)
		{
			for(int j=p;j>=0;j--)
			{
				if(j>=cnt[i][0]&&dp[i-1][j-cnt[i][0]])//必须要保证dp[i-1][j]存在
				{
					dp[i][j]+=dp[i-1][j-cnt[i][0]];
					pre[i][j]=cnt[i][0];
				}
				if(j>=cnt[i][1]&&dp[i-1][j-cnt[i][1]])
				{
					dp[i][j]+=dp[i-1][j-cnt[i][1]];
					pre[i][j]=cnt[i][1];
				}
			}
		}
		vector<int> ans;//记录选取的好人集合 
		ans.clear();//多组输入,不要忘记清空答案数组
		if(dp[count][p]!=1)
			puts("no");
		else
		{
			while(count)
			{
				if(cnt[count][0]==pre[count][p])//必须从每个集合中选出一个集合作为好人 
				{
					p-=cnt[count][0];
					for(int i=0;i<cnt[count][0];i++)
						ans.push_back(s[count][0][i]);
				}
				else(cnt[count][1]==pre[count][p])
				{
					p-=cnt[count][1];
					for(int i=0;i<cnt[count][1];i++)
						ans.push_back(s[count][1][i]);
				}
				count--;
			}
			sort(ans.begin(),ans.end());
			for(int i=0;i<ans.size();i++)
				printf("%d\n",ans[i]);
			puts("end");
		}
	}
	return 0;
}
举报

相关推荐

0 条评论