zoukankan      html  css  js  c++  java
  • 真正的骗子(并查集+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;
    }
    

      

    ```

    ----------

  • 相关阅读:
    Java规约之方法设计
    JVM第一篇 JVM与Java体系结构
    初学者学习Java方法集
    初学者学习Java方法集
    1.Spring-Boot 静态文件和页默认放置位置
    2.Spring-Boot常用配置解释
    3.Spring-Boot核心@SpringBootApplication介绍
    4.Spring-Boot中基于Junit的单元测试
    vue学习笔记(一) ---- vue指令(v-for 和 key 属性)
    vue学习笔记(一) ----- vue指令(菜单列表案例)
  • 原文地址:https://www.cnblogs.com/2aptx4869/p/12400567.html
Copyright © 2011-2022 走看看