在一艘小船上漂流了几天之后,Akira Crusoe Maeda 被困在一个雾气弥漫的小岛上。尽管他已经筋疲力尽,但他仍然想起了他童年时从族长那里听到的雾岛的传说,那个传说中的岛屿。在传说中,有两个部落居住在岛上,一个是神族,另一个是恶魔,一旦神圣部落的成员祝福你,你的未来会变得光明而充满希望,灵魂最终会升入天堂,相反,一旦恶魔部落的成员诅咒你,你的未来会变得凄凉无望,灵魂最终会沉入地狱。
为了防止最坏的情况发生,Akira 要区分恶魔与神族。但是他该怎么做呢?这些人看起来完全相同,他不能仅仅通过他们的外表来区分彼此。然而,他仍然有最后的办法。神圣部落的成员都是诚实的,也就是说,他们总是说实话。而恶魔部落的成员都是骗子,也就是说,他们总是说谎。
Akira 问这些人中的某些人是否神圣。这些人非常了解彼此并且总是根据他们的特质“忠实地”回应他(他们总是说实话或总是撒谎)。他不敢问任何其他别的的问题,因为传说中一个恶魔会在他不喜欢这个问题时永远诅咒提问的人。他还有另一条重要的线索:传说提到了两个部落的人口。因为生活在这个岛上的每个人都是不朽的,所以这个数字是值得信赖的。至少在这几千年里岛上的人口都没有改变过。
你是一个优秀的计算机程序员,请你帮助 Akira 编写一个程序,根据他的问题答案对居民进行分类,帮助他脱离困境。
输入
输入格式如下:
n p1 p2
xl yl a1
x2 y2 a2
...
xi yi ai
...
xn yn an
第一行有三个非负整数 n, p1, 和 p2. n 是 Akira 问的问题数. p1 和 p2 是在传说中记载的神族和恶魔族的人口数量。下面的每 n 行包括两个整数xi, yi和一个单词 ai。 xi 和 yi 是每个人的编号,号码范围从 1 到 p1 + p2。 ai 可能是 yes 或者 no。如果居民 xi 想让你认为 yi 是神族,他会回答 yes。注意:xi 和 yi 可能相同。所以”你是神族吗?“是一个有效的问题。还要注意,Akira 十分紧张,所以相同的问题可能重复出现。
数据范围:
n < 1000
p1,p2 < 300
出现 0 0 0 的一行标示数据结束。 数据保证成立,没有矛盾数据。
输出
对于每个数据集,如果它包含足够的信息来对所有居民进行分类,则按升序打印所有神族居民的编号,一行一个。 在输出数字后再输出end。 如果给定数据集不包括足以识别所有神族的信息,输出no。
样例输入
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
样例输出
no no 1 2 end 3 4 5 6 end
题目分析:有一点十分重要的是题目告诉我们神说真话,恶魔说假话,所以我们可以先假设:
(1)YES情况: A说B是神,如果A是神,那么A说真话所以B也是神,A是恶魔,A说假话,B也 是恶魔
B是神,A说B是神,A说的是真话吗,则A是神,B是恶魔,A说B是神,A说假 话,A是恶魔
(2)NO情况: A说B是恶魔,如果A是恶魔,A说假话,那么B是神,反之如果B是神,A说的 是假话,A是恶魔
假设A是神,那么A说真话,B是恶魔,如果B是恶魔,A说的是真话,A是神
从上面的分析中我们可以得出如果是YES的情况,那么2者一定是同一类,如果是NO的情况,那2者一定是不同类,一说的归类我们就很容易想到并查集,那问题来了,如果是同一类我们就把2者合并,那如果是不同类呢?不做任何处理显然是不对的,所以我们就需要用一个rel数组来记录当前节点与根节点的关系,rel[x]==0说明x与根节点是同一类,rel[x]==1说明x与根节点不同类,我们这里是用异或来处理子节点和根节点的关系(之前好像做过类似的用异或来处理关系的题目,不过本人太菜了又给忘了。。。。,最后还是看的别的大佬的博客),做完了这一步后现在我们得到了一堆一堆的集合,现在的任务是从这些集合中选出特定的集合来凑出神,然后想到了01背包问题,不过这里和01背包有一点些许的不同就是01背包是当前物品有2种选择选或者是不选,而我们这里是有3种,选同类,选不同类,不选,这里我们设dp[i][j]表示神有i人恶魔有j人的情况的种类,初始是dp[0][0]==12者都是0人的时候就1中情况喽,做完之后如果dp[神总人数][恶魔总人数]!=1可以直接输出no,如果不等于1,这里就有坑点了,之前我们用01背包算的dp数组中的情况可能会有被覆盖的情况,所以就需要记录一下路径,具体的就看代码吧,
说明一下这个代码是我看着另一位大佬的博客写的,但是我又给找不到了QAQ。。。,求勿喷
#include <iostream>
#include <cstring>
#include <cstdio>
using namespace std;
const int N=610;
int m,n,p1,p2,scc;
int p[N],rel[N];
int num[N][2],root[N],dp[N][N];//num[i][j]记录的是第i个集合情况是j的集合元素的个数,root是记录每一组的根节点
int pre[N][N];//用来记录路径
bool ans[N];
int find(int x)
{
if(p[x]!=x)
{
int t=find(p[x]);
rel[x]=rel[x]^rel[p[x]];
p[x]=t;
}
return p[x];
}
void solve()
{
int i,j,k,x,y;
memset(dp,0,sizeof dp);
dp[0][0]=1;
for(i=1;i<=scc;i++)
{
for(j=p1;j>=0;j--)
for(k=p2;k>=0;k--)
{
x=j-num[i][0],y=k-num[i][1];
if(x>=0&&y>=0&&dp[x][y])
dp[j][k]+=dp[x][y];
x=j-num[i][1],y=k-num[i][0];
if(x>=0&&y>=0&&dp[x][y])
dp[j][k]+=dp[x][y];
}
}
if(dp[p1][p2]!=1)
cout<<"no"<<endl;
else
{
memset(pre,0,sizeof pre);
memset(dp,0,sizeof dp);
dp[0][0]=1;
for(i=1;i<=scc;i++)
{
for(j=p1;j>=0;j--)
for(k=p2;k>=0;k--)
{
if(dp[j][k])
continue;//这一步很重要,没有这一步会疯狂RE,找了好几遍才发现
x=j-num[i][0],y=k-num[i][1];
if(x>=0&&y>=0&&dp[x][y])
{
dp[j][k]+=dp[x][y];
pre[j][k]=0;
continue;
}
x=j-num[i][1],y=k-num[i][0];
if(x>=0&&y>=0&&dp[x][y])
{
dp[j][k]+=dp[x][y];
pre[j][k]=1;
continue;
}
}
}
x=p1;
y=p2;
memset(ans,false,sizeof ans);
for(i=scc;i>=1;i--)
{
for(j=1;j<=n;j++)
if(p[j]==root[i]&&pre[x][y]==rel[j])
ans[j]=true;
if(!pre[x][y])
x-=num[i][0],y-=num[i][1];
else
x-=num[i][1],y-=num[i][0];
}
for( i=1;i<=n;i++)
{
if(ans[i])
cout<<i<<endl;
}
cout<<"end"<<endl;
}
//
return ;
}
int main()
{
while(scanf("%d%d%d",&m,&p1,&p2)!=EOF)
{
if(m==0&&p1==0&&p2==0)
break;
int i,j,u,v,w;
char s[10];
n=p1+p2;
memset(num,0,sizeof num);
for( i=1;i<=n;i++)
{
p[i]=i;
rel[i]=0;
}
for( i=1;i<=m;i++)
{
scanf("%d%d%s",&u,&v,s);
w=(s[0]=='n');
int du=find(u),dv=find(v);
if(du!=dv)
{
p[du]=dv;
rel[du]=rel[u]^rel[v]^w;
}
}
for( i=1;i<=n;i++)
p[i]=find(i);
scc=0;
for( i=1;i<=n;i++)
{
if(p[i]==i)
{
scc++;
root[scc]=i;
for(j=1;j<=n;j++)
if(p[j]==i)
num[scc][rel[j]]++;
}
}
solve();
}
return 0;
}