真正的騙子(並查集+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\n", i);
                else if (fi <= sum && a[vis[fi]] == -1) printf("%d\n", i);
        puts("end");
    }
    return 0;
}

  

```

----------


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM