真正的骗子(并查集+dp+dp状态回溯)

[//]: # (推荐题解模板,请替换blablabla等内容 ^^)

### 题目描述

一个岛上存在着两种居民,一种是天神,一种是恶魔。

天神永远都不会说假话,而恶魔永远都不会说真话。

岛上的每一个成员都有一个整数编号(类似于身份证号,用以区分每个成员)。

现在你拥有n次提问的机会,但是问题的内容只能是向其中一个居民询问另一个居民是否是天神,请你根据收集的回答判断各个居民的身份。

输入格式

输入包含多组测试用例。

每组测试用例的第一行包含三个非负整数n,p1,p2p1,p2,其中n是你可以提问的总次数,p1p1是天神的总数量,p2p2是恶魔的总数量。

接下来n行每行包含两个整数xi,yixi,yi以及一个字符串aiai,其中xi,yixi,yi是岛上居民的编号,你将向编号为xixi的居民询问编号为yiyi的居民是否是天神,

aiai是他的回答,如果aiai为“yes”,表示他回答你“是”,如果aiai为“no”,表示他回答你“不是”。

xi,yixi,yi可能相同,表示你问的是那个人自己是否为天神。

当输入为占据一行的“0 0 0”时,表示输入终止。

输出格式

对于每组测试用例,如果询问得到的信息足以使你判断每个居民的身份,则将所有天神的编号输出,每个编号占一行,在输出结束后,在另起一行输出“end”,表示该用例输出结束。

如果得到的信息不足以判断每个居民的身份,则输出“no”,输出同样占一行。

数据范围

1xi,yiq1+q21≤xi,yi≤q1+q2,
1n<1000,1p1,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


```


----------

### 算法1
##### (并查集+dp+路径还原)

前置知识 并查集(食物链 那道题要会做),dp的路径还原(可以状态回溯)

1.对于 a b yes, 我们合并(a,b)和(a + p1 + p2, b + p1 + p2)
代表a,b一体,如果a是好人则b是好人,如果a(a + p1 + p2)是坏人,则b(b + p1 + p2)是坏人
2.对于 a b no, 合并(a, b + p1 + p2), (a + p1 + p2, b)含义和上面一样

仅有人之间的指认关系是不知道答案的,我们要找几个集合,恰好且仅有一种方法能凑出 p1 人才有答案

对于每个人,要么是好人,要么是坏人(废话,下面是关键)
所以对于每个人,
1.这个人 i 所在的集合全是好人,与 i + p1 + p2 处于一个集合的都是坏人
即father[其他人] == father[i] 的 都是好人, father[其他人] == father[i + p1 + p2] 的 都是坏人
2. 这个人 i 是坏人,………………

每个大集合分为,这个集合的father是好人, father是坏人

所以dp的时候必须从上个状态转移(不存在这个人及不是好人也不是坏人在状态转换)不能直接继承

对于前i个集合可拼成 j个好人, 转移:
dp[i][j] += dp[i - 1][j - 集合的father是好人时好人人数]
dp[i][j] += dp[i - 1][j - 集合的father是坏人时好人人数]
dp[i][j] 存的是能满足前i个集合可拼成 j个好人的方法数,只有当(dp[集合个数][p1] == 1)能唯一确定谁是好人,谁是坏人

dp状态的回溯见代码,毕竟 (dp[集合个数][p1] == 1) 路径是唯一的很简单


#### C++ 代码
```

#include <bits/stdc++.h>
using namespace std;

int f[1205], s[1205], n, x, y, sum, h[1205];
int tot, a[605], vis[1205], b[605], dp[605][605];
char str[5];

int find(int x)
{
    if (x == f[x]) return x;
    return f[x] = find(f[x]);
}

void unit(int x, int y)
{
    x = find(x), y = find(y);
    if (x == y) return;
    if (h[x] > h[y]) s[x] += s[y], f[y] = x;
    else if (h[x] < h[y]) s[y] += s[x], f[x] = y;
    else s[x] += s[y], f[y] = x, ++ h[x];
}

void init()
{
    memset(dp, 0, sizeof dp);
    sum = x + y, tot = 0, dp[0][0] = 1;
    for (int i = 1;  i<= sum; ++ i) 
    {
        f[i] = i, f[i + sum] = i + sum;
        s[i] = h[i] = h[i + sum] = 1;
        s[i + sum] = vis[i] = vis[i + sum] = 0;
    }
}

int main()
{
    while(scanf("%d%d%d", &n, &x, &y), n + x + y)
    {
        init();

        for (int i = 1, a, b;  i <= n; ++ i)
        {
            scanf("%d%d%s", &a, &b, str + 1);
            if (str[1] == 'y') unit(a, b), unit(a + sum, b + sum);
            else unit(a, b + sum), unit(a + sum, b);       
        }


        for (int i = 1, fi; i<= sum; ++ i) 
        {
            if((fi = find(i)) != i) continue;
            vis[fi] = vis[fi + sum] = ++ tot;
            a[tot] = s[fi], b[tot] = s[fi + sum];   
        }


        for (int i = 1; i <= tot; ++ i)
        {
            for (int j = min(a[i], b[i]); j <= x; ++ j)
            {
                if (j >= a[i]) dp[i][j] += dp[i - 1][j - a[i]];
                if (j >= b[i]) dp[i][j] += dp[i - 1][j - b[i]];
            }
        }


        if (dp[tot][x] != 1) {puts("no"); continue;}


        for (int i = tot; i; -- i)
            if (dp[i - 1][x - a[i]]) x -= a[i], a[i] = -1;
            else if (dp[i - 1][x - b[i]]) x -= b[i], a[i] = -2;


        for (int i = 1, fi = find(1); i <= sum; fi = find(++ i))
            if(vis[fi])
                if (fi > sum && a[vis[fi]] == -2) printf("%d
", i);
                else if (fi <= sum && a[vis[fi]] == -1) printf("%d
", i);
        puts("end");
    }
    return 0;
}

  

```

----------

原文地址:https://www.cnblogs.com/2aptx4869/p/12400567.html