题意:
题目中告诉两种人,一种只说真话,一种只说假话。然后告诉n个描述,每个描述是说a说b是说真话的人(yes)或者是说假话的人(no),最后问是否能判断哪些人是只说真话的那类人。
思路:
根据题意可知:其中好人说真话,坏人说假话这点很重要。
那么如果一个人说另一个人是好人,那么如果这个人是好人,说明 对方确实是好人,如果这个是坏人,说明这句话是假的,对方也是坏人。
如果一个人说另一个人是坏人,那么如果这个人是好人,说明对方是坏人,如果这个是坏人,说明 对方是好人。
也就是如果条件是yes说明这两个是相同集合的,否则是两个不同的集合
然后我们通过并查集把有关系的一些人合并到一个集合内。
然后问题就转化为:有n个集合,每个集合都有a, b连个数字,现在要求n个集合中各跳出一个数(a或者b),使得他们之和等于n1(说真话的人数)。
然后用背包来解决这个问题,但答案要求输出所有说真话的人的编号,这就麻烦点了,我们要记录每个集合中的人的状态,还要记录dp的路径。具体方法见代码
代码如下:
const int M = 605;
int p[M], flag[M], dp[M][M/2], left[M][M/2];
int find(int x)
{
int tmp = p[x];
p[x] = (p[x] == x ? x : find(p[x]));
flag[x] = flag[tmp]^flag[x];
return p[x];
}
int main()
{
int n, p1, p2, a, b, o;
char tmp[10];
while(scanf("%d%d%d", &n, &p1, &p2) && n+p1+p2)
{
for(int i = 1; i <= p1+p2; ++i) p[i] = i;
for(int i = 0; i < n; ++i)
{
scanf("%d%d %s", &a, &b, tmp);
o = strcmp("yes", tmp)==0?0:1;
int x = find(a);
int y = find(b);
if(x != y)
{
p[x] = y;
flag[x] = flag[a]^flag[b]^o;
}
}
vector<int>A[M];
vector<int>B[M];
int vis[M], cnt = 1;
memset(vis,0,sizeof(vis));
for(int i = 1; i <= p1+p2; ++i)
{
if(!vis[i])
{
int f = find(i);
for(int j = i; j <= p1+p2; ++j)
if(find(j)==f)
{
vis[j] = 1;
flag[j]==0?A[cnt].push_back(j):B[cnt].push_back(j);
}
++cnt;
}
}
memset(dp,0,sizeof(dp));
memset(left,0,sizeof(left));
dp[0][0] = 1;
for(int i = 1; i < cnt; ++i)
{
int la = A[i].size();
int lb = B[i].size();
for(int j = p1; j >= la||j >= lb; --j)
{
if(j>=la && dp[i-1][j-la]) dp[i][j] += dp[i-1][j-la], left[i][j] = j-la;
if(j>=lb && dp[i-1][j-lb]) dp[i][j] += dp[i-1][j-lb], left[i][j] = j-lb;
}
}
vector<int>ans;
if(dp[cnt-1][p1]!=1) printf("no\n");
else
{
int tt = p1;
for(int i = cnt-1; i >= 1; --i)
{
int dt = tt-left[i][tt];
if(dt==(int)A[i].size())
for(int j = 0; j < (int)A[i].size(); ++j) ans.push_back(A[i][j]);
else
for(int j = 0; j < (int)B[i].size(); ++j) ans.push_back(B[i][j]);
tt = left[i][tt];
}
sort(ans.begin(), ans.end());
for(int i = 0; i < (int)ans.size(); ++i) printf("%d\n", ans[i]);
printf("end\n");
}
}
return 0;
}