POJ 1417 True Liars

参考自大佬的博客:https://blog.csdn.net/gg_gogoing/article/details/38753567

题目

题面

一个岛上有神与恶魔两个种族,神会说真话,恶魔会说假话。已知神与恶魔的个数,但不知道具体个人是属于哪个。

n,x,y

这个人问n次 ,x为神的个数,y为恶魔的个数。

每次的问题为 xi,yi,a 问xi ,yi是否为神? a为yes/no。注意xi,yi可能为同一个人

若最终可得出哪些是神则从小到大输出神的编号,并最终输出end

否则输出no  

Input

输入格式如下:

1
2
3
4
5
6
7
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 的一行标示数据结束。 数据保证成立,没有矛盾数据。

Output

对于每个数据集,如果它包含足够的信息来对所有居民进行分类,则按升序打印所有神族居民的编号,一行一个。 在输出数字后再输出end。 如果给定数据集不包括足以识别所有神族的信息,输出no。

Examples

Input

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

Output

1
2
3
4
5
6
7
8
9
10
no
no
1
2
end
3
4
5
6
end

思路

经过简单的推理可得只要说了‘yes’那么那两个就属于同一类,‘no’则相反
使用并查集表名它们的关系,用背包统计在不同情况下有多少种答案。
最后进行答案统计。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
#include<iostream>
#include<string.h>
#include<algorithm>
using namespace std;
int pr[1010], relation[1010];
int kind[1010], cnt[2010], dp[1010][1010];//dp[i][j],代表一共i个头节点剩余容量为j时,一共有多少情况
int p[1010][1010];//p[i][j]代表一共i个头节点剩余容量为j时,在背包中的元素是不是与第i个头节点处于同一类
int x, y, z;
int ans[1010], f;
int find(int xx)
{
if (pr[xx] == -1)
{
return xx;
}
int tmp = find(pr[xx]);
relation[xx] = (relation[xx] + relation[pr[xx]]) % 2;//带权并查集,推导出与头节点的关系
pr[xx] = tmp;
return tmp;
}
int solve()
{
memset(dp, 0, sizeof(dp));
memset(p, 0, sizeof(p));
memset(cnt, 0, sizeof(cnt));
f = 0;
int k = 0;
for (int i = 1; i <= x + y; i++)//压缩路径
find(i);
for (int i = 1; i <= x + y; i++)
{
if (pr[i] == -1)
kind[k++] = i;//看看有多少头节点结点
}
for (int i = 0; i < k; i++)
{
cnt[i * 2] = 1;
for (int j = 1; j <= x + y; j++)
{
if (pr[j] == kind[i])
{
if (relation[j] == 0)
cnt[i * 2]++;//记录与kind[i]的头节点有多少节点与它处于同一类
else
cnt[i * 2 + 1]++;//记录与kind[i]的头节点有多少节点不与它处于同一类
}
}
}
dp[0][cnt[0]]++; p[0][cnt[0]] = 0;//初始化第0个头节点
dp[0][cnt[1]]++; p[0][cnt[1]] = 1;
for (int i = 1; i < k; i++)
for (int j = x; j >= 0; j--)
{
if (j >= cnt[2 * i] && dp[i - 1][j - cnt[2 * i]] )//01背包
{
dp[i][j] += dp[i - 1][j - cnt[2 * i]];
p[i][j] = 0;
}
if (j >= cnt[2 * i + 1] && dp[i - 1][j - cnt[2 * i + 1]] )
{
dp[i][j] += dp[i - 1][j - cnt[2 * i + 1]];
p[i][j] = 1;
}
}
if (dp[k - 1][x] != 1)
return 0;
int m = x, t;
f = 0;
for (int i = k - 1; i >= 0; i--)
{
if (p[i][m] == 0)//看背包中的元素与第i个头节点同类还是相反
{
t = 0; m = m - cnt[i * 2];
}
else
{
t = 1; m = m - cnt[2 * i + 1];
}
for (int j = 1; j <= x + y; j++)
{
if (pr[j] == kind[i] || j == kind[i])//统计个数
{
if (relation[j] == t)
ans[f++]=j;
}
}
}
sort(ans, ans + f);
return 1;
}
int main()
{
//freopen("F:/y.txt", "r", stdin);
while (cin >> z >> x >> y)
{
if (x == 0 && y == 0 && z == 0)
break;
memset(pr, -1, sizeof(pr));
memset(relation, 0, sizeof(relation));
for (int i = 0; i < z; i++)
{
int a, b;
char s[10];
cin >> a >> b >> s;
if (a < b)
{
int tmp = a; a = b; b = tmp;
}
int aa = find(a), bb = find(b);
if (aa == bb)
{
continue;
}
if (s[0] == 'y')
{
pr[aa] = bb;
relation[aa] = (relation[a] + relation[b]) % 2;//带权并查集
}
else
{
pr[aa] = bb;
relation[aa] = (relation[a] + relation[b] + 1) % 2;//带权并查集
}
}
if (x == y)
{
cout<<"no"<<endl; continue;
}
if (solve())//f可能为0
{
for (int i = 0; i < f; i++)
{
cout << ans[i]<<endl;
}
cout << "end" << endl;
}
else
cout << "no" << endl;
}
return 0;
}