真正的骗子(并查集+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”,输出同样占一行。
数据范围
1≤xi,yi≤q1+q21≤xi,yi≤q1+q2,
1≤n<1000,1≤p1,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++ 代码
```
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 | #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; } |
```
----------
本文作者:洛绫璃
本文链接:https://www.cnblogs.com/2aptx4869/p/12400567.html
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 用 C# 插值字符串处理器写一个 sscanf
· Java 中堆内存和栈内存上的数据分布和特点
· 开发中对象命名的一点思考
· .NET Core内存结构体系(Windows环境)底层原理浅谈
· C# 深度学习:对抗生成网络(GAN)训练头像生成模型
· 为什么说在企业级应用开发中,后端往往是效率杀手?
· 本地部署DeepSeek后,没有好看的交互界面怎么行!
· 趁着过年的时候手搓了一个低代码框架
· 用 C# 插值字符串处理器写一个 sscanf
· 推荐一个DeepSeek 大模型的免费 API 项目!兼容OpenAI接口!