一个岛上存在着两种居民,一种是天神,一种是恶魔。
天神永远都不会说假话,而恶魔永远都不会说真话。
岛上的每一个成员都有一个整数编号(类似于身份证号,用以区分每个成员)。
现在你拥有 nn 次提问的机会,但是问题的内容只能是向其中一个居民询问另一个居民是否是天神,请你根据收集的回答判断各个居民的身份。
输入格式
输入包含多组测试用例。
每组测试用例的第一行包含三个非负整数 n,p1,p2n,p1,p2,其中 nn 是你可以提问的总次数,p1p1 是天神的总数量,p2p2 是恶魔的总数量。
接下来 nn 行每行包含两个整数 xi,yixi,yi 以及一个字符串 aiai,其中 xi,yixi,yi 是岛上居民的编号,你将向编号为 xixi 的居民询问编号为 yiyi 的居民是否是天神,
aiai 是他的回答,如果 aiai 为 yes
,表示他回答你“是”,如果 aiai 为 no
,表示他回答你“不是”。
xi,yixi,yi 可能相同,表示你问的是那个人自己是否为天神。
当输入为占据一行的 0 0 0
时,表示输入终止。
输出格式
对于每组测试用例,如果询问得到的信息足以使你判断每个居民的身份,则将所有天神的编号输出,每个编号占一行,在输出结束后,在另起一行输出 end
,表示该用例输出结束。
如果得到的信息不足以判断每个居民的身份,则输出 no
,输出同样占一行。
数据范围
1≤xi,yi≤p1+p21≤xi,yi≤p1+p2,
0≤n<1000,0≤p1,p2<3000≤n<1000,0≤p1,p2<300
输入样例:
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
分析
对于询问只有四种情况
天神 天神 yes
天神 恶魔 no
恶魔 天神 no
恶魔 恶魔 yes
即两人同类是yes,异类为no。用带权并查集来维护所有人的关系,每个集合根节点记为0,子节点与父节点同类值为0,子节点与父节点异类则值为1。对于以下的关系有1、3、6为同类,2、4、5为另一类。
用带权并查集存储关系,f[i]=j表示i的父节点为j, d[i] = 1表示与父节点不同类, d[i] = 0表示与父节点同类
路径压缩:对于节点6压缩后直接连到节点1,更新f[6] = 1, d[6] = d[6] ^ d[4] = 0
合并:x和y同类op=0,x和y异类op=1
f[fy] = fx ; d[fy] = d[y] ^ d[x] ^ op
对于数据4
5 4 3
1 2 yes
1 3 no
4 5 yes
5 6 yes
6 7 no
集合1 1 2为同类,3为另一类
集合2 4 5 6为同类,7为另一类
每一个集合都有两种情况,对于集合1, 1 2可能为天神3为恶魔,或者1 2为恶魔3为天神。
动态规划统计出(p, q)这种情况的个数,如果为1则可以确定每个人的身份。
回溯确定每个集合根节点的身份,即可确定每个人的身份
例:dp(4,3)=1,最后一次选的是集合2,上一状态只能是dp(1,2)和dp(3,1),dp(3,1)=0而dp(1,2)=1说明集合2中4 5 6是天神、7是恶魔
参考代码
#include <iostream>
#include <cstring>
using namespace std;
const int N = 310;
int n, p, q, x, y;
int f[2*N], d[2*N], dp[N][N], a[2*N][2], val[2*N];
string str;
bool op;
int find(int x){//并查集带路径压缩
if(x!=f[x]){
int u = find(f[x]);
d[x] ^= d[f[x]];
f[x] = u;
}
return f[x];
}
void print(int x, int y, int top){//回溯
if(x==0 && y==0) return;
while(top && !a[top][0] && !a[top][1]) top--;
int i = a[top][0], j = a[top][1];
if(x-j>=0 && y-i>=0 && dp[x-j][y-i]==1) {
val[top] = 0;
print(x-j, y-i, top-1);
}
else if(x-i>=0 && y-j>=0 && dp[x-i][y-j]==1){
val[top] = 1;
print(x-i, y-j, top-1);
}
}
int main(){
while(cin >> n >> p >> q){
if(n==0 && p==0 && q==0) break;
for(int i=0; i<=p+q; i++){
f[i] = i;
d[i] = 0;
}
while(n--){//读入数据,并合并集合
cin >> x >> y >> str;
if(str=="yes") op = 0;
else op = 1;
int fx = find(x), fy = find(y);
if(fx!=fy){
f[fy] = fx;
d[fy] = d[x]^d[y]^op;
}
}
memset(val, 0, sizeof val);
memset(dp, 0, sizeof dp);
memset(a, 0, sizeof a);
for(int i=1; i<=p+q; i++){//统计每个集合天神和恶魔的人数
x = find(i);
a[x][d[i]]++;
}
int cnt = 0;
dp[0][0] = 1;
for(int i=1; i<=p+q; i++){//计算dp(i, j),i个天生、j个恶魔的可能个数
if(a[i][0] || a[i][1]){
cnt += a[i][0] + a[i][1];
for(int j=p, k; j>=0; j--){
k = cnt - j;
if(k<0) continue;
if(j-a[i][0]>=0 && k-a[i][1]>=0){
dp[j][k] += dp[j-a[i][0]][k-a[i][1]];
}
if(j-a[i][1]>=0 && k-a[i][0]>=0)
dp[j][k] += dp[j-a[i][1]][k-a[i][0]];
}
}
}
if(dp[p][q]==1) {//dp(p, q)只有1种情况,即可确定每个人身份
int cnt = 0;
for(int i=1; i<=p+q; i++) cnt += d[i];
if(cnt == p) op = 1;
else op = 0;
print(p, q, p+q);
for(int i=1; i<=p+q; i++){
int u = find(i);
if(val[u]^d[i])
cout << i << endl;
}
cout << "end" << endl;
}
else cout << "no" << endl;
}
return 0;
}