题目:True Liars

题目大意:给出n对关系,p1个好人,p2个坏人。要求根据n对关系中找出好人有哪些,若方案唯一,则逐个输出好人,最后输出end;若方案不唯一/找不到,那么输出no

结论:通过简单的分析可以得出,对于每对关系(x,y,yes/no),若关系为yes,则x和y属于同一类人;若关系为no,则x和y属于相反类人。

解题思路:根据初步的n对关系,我们只可以推出哪些属于同一类人,哪些属于相反类人,但不可以确定到底哪类是好人/坏人。我们可以将相反的两类人确定为一个大集合,那么大集合里面就有两个小集合。根据题意,我们只需要从每个大集合中,选其中一个小集合,使得选中的所有小集合的人数总和为p1即可。

#include <cstdio>
#include <cstring>
#include <iostream>
#define MAXN 600
using namespace std;int parent[MAXN], path[MAXN][MAXN], dp[MAXN][MAXN];
int num[MAXN];      //记录大集合的根节点为i的是第num[i]个大集合
int flag[MAXN][2];  //标记每个大集合中选中到最终结果的小集合
int cnt[MAXN][2];  //根节点为i时,分别记录两个小集合包含的人数
int relat[MAXN];
/* relation* relat=0/1。0表示i和根节点parent[i]相关联,即属于同一个小集合;1表示i和根节点不关联,即属于同一个大集合中的不同小集合*/int find(int x) {if (x != parent[x]) {          //此处的parent[x]是旧父int px = find(parent[x]);  // px是新父relat[x] ^= relat[parent[x]];/* 由于路径压缩要将x的父亲节点直接指向根节点,即更改了x的父亲节点,所以要更新节点x和根节点的关系。*/parent[x] = px;}return parent[x];
}
/*
对于"relat[x] ^= relat[parent[x]];"异或解释:可以通过简单的分析得出:1.如果( relation[x和旧父]=0 && relation[旧父和新父]=0 )或者(
relation[x和旧父]=1 && relation[旧父和新父]=1 ),则 relation[x和新父]=02.如果(relation[x和旧父]=1 && relation[旧父和新父]=0 )或者(
relation[x和旧父]=0 && relation[旧父和新父]=1 ),则 relation[x和新父]=1可以发现,最终relatino[x和新父] = relation[x和旧父] ^ relation[旧父和新父]即: relat[x] ^= relat[parent[x]];对于下面的merge函数中的异或同理*/
void merge(int x, int y, int d) {  // d要么为0,要么为1int px = find(x);int py = find(y);if (px != py) {parent[py] = px;relat[py] = relat[x] ^ relat[y] ^ d;}
}int main(void) {int m, p, q;while (scanf("%d%d%d", &m, &p, &q)) {if (m + p + q == 0) {break;}for (int i = 1; i <= p + q; i++) {  //**初始话relat[i] = 0;parent[i] = i;}while (m--) {int x, y, d = 1;char ch[5];scanf("%d%d%s", &x, &y, ch);if (ch[0] == 'y') {d = 0;}merge(x, y, d);}memset(num, 0, sizeof(num));  //**num存储集合个数并且给他们编号memset(path, 0, sizeof(path));memset(cnt, 0, sizeof(cnt));memset(dp, 0, sizeof(dp));memset(flag, 0, sizeof(flag));int tot = 0;for (int i = 1; i <= p + q; i++) {  //**统计集合个数并且编号if (find(i) == i) {num[i] = ++tot;  // tot连通块个数,num记录root=i的连通块的编号}}for (int i = 1; i <= p + q;i++) {  //**分别统计每个集合两种类的数目并存储到cnt中cnt[num[find(i)]][relat[i]]++;}dp[0][0] = 1;for (int i = 1; i <= tot; i++) {        // tot=2for (int j = p + q; j >= 0; j--) {  // p+q=7//**dp[i][j]存储到第i个集合选择种类和为j的方法数if (j - cnt[i][0] >= 0 && dp[i - 1][j - cnt[i][0]]) {dp[i][j] += dp[i - 1][j - cnt[i][0]];path[i][j] =cnt[i][0];  //**path数组记录路径,即选的是1还是0}if (j - cnt[i][1] >= 0 && dp[i - 1][j - cnt[i][1]]) {dp[i][j] += dp[i - 1][j - cnt[i][1]];path[i][j] = cnt[i][1];}}}/*这部分两个if有点难理解,因为可能会想到,两个if都满足的话,那么最后得出的dp[i][j]就有可能存在同一个大集合里面选取了两个小集合的情况,这样就违背了“只从一个大集合选其中一个小集合”的情况。但,可以发现,我们最后限制了方法数是dp[tot][p]=1时,才是有唯一方案的。如果像刚刚说的两个if都满足,那么dp[i][j]一定>1,也就不会符合下面的dp[tot][p]=1限制,所以解决了这个问题。*/if (dp[tot][p] != 1) {printf("no\n");} else {for (int i = tot, j = p; j > 0 && i > 0; i--) {  //**标记路径if (path[i][j] == cnt[i][0]) {flag[i][0] = 1;} else {flag[i][1] = 1;}j -= path[i][j];//记得减。因为背包时恰好选够p,所以要跟随着p逐次减少当时的物品,找到路径}for (int i = 1; i <= p + q; i++) {if (flag[num[find(i)]][relat[i]]) {printf("%d\n", i);}}printf("end\n");}}return 0;
}/*5 4 3 1 2 yes 1 3 no 4 5 yes 5 6 yes 6 7 no  0 0 06 4 4 1 2 yes 1 3 no 4 5 yes 5 6 yes 6 7 no  7 8 yes 0 0 0*/

POJ 1417 True Liars (种类并查集+DP)相关推荐

  1. POJ1417 True Liars ——种类并查集+01背包+路径** 好题

    ​​​​​​POJ1417 题意: 有n行输入形如x, y, str,str为yes表示x说y是天使,str为no表示x说y不是天使(x, y为天使,恶魔的编号,1<=x,y<=p+q): ...

  2. POJ 1417 True Liars(路径压缩并查集+DP背包问题)

    POJ 1417 True Liars(路径压缩并查集+DP背包问题) http://poj.org/problem?id=1417 题意: 给出p1+p2个人,其中p1个是好人,p2个是坏人.然后有 ...

  3. POJ - 1417(True Liars)

    题意:雾岛上有两个部落,天使部落有 p1 个人,他们只说真话,恶魔部落有 p2 个人,他们只说假话,但是两个部落的人长得一模一样,把这 p1+p2 个人从1编号,然后有 n 次询问,每次询问给出 a ...

  4. POJ 1417 True Liars 并查集+背包

    题目链接:http://poj.org/problem?id=1417 解题思路:比较容易想到的是并查集,然后把第三组数据测试一下之后发现这并不是简单的并查集,而是需要合并之后然后判断的.并且鉴于题目 ...

  5. POJ - 1417 True Liars POJ - 141 带权并查集,01背包问题

    题目链接 POJ-1417 题意 岛上有说真话的好人和说假话的坏人,给你这两种人的人数.再给出q次问答结果,问答的格式是向a询问b是否是好人,回答是yes或者no.问是否可以分辨出全部好人,是的话打印 ...

  6. [POJ 1417] True Liars

    [题目链接] http://poj.org/problem?id=1417 [算法]  首先,我们发现 :  如果A说B是好人,那么A和B是同一类人,否则A和B不是同一类人          利用这个 ...

  7. POJ 1417 True Liars 带权并查集 + 背包

    一.内容 After having drifted about in a small boat for a couple of days, Akira Crusoe Maeda was finally ...

  8. POJ 1417 True Liars(带权并查集+DP)

    传送门 Description After having drifted about in a small boat for a couple of days, Akira Crusoe Maeda ...

  9. C - BLG POJ - 1417 种类并查集加dp(背包)

    思路:刚看这道题感觉什么都不清楚,人物之间的关系一点也看不出来,都不知道怎么写,连并查集都没看出来,但是你可以仔细分析一下,当输入字符串为"yes"的时候,我们设输入的值为x和y, ...

最新文章

  1. 设置固定长度_加气块砌筑(构造柱、圈梁设置)技术交底21条
  2. java ajax 更改头像_ajax+node实现头像更改
  3. mysql主从复制服务器配置
  4. 黑马程序员_java基础笔记(03)...面向对象
  5. GraphQL教程(三) .net core api
  6. 国内python镜像源记录
  7. PHP算法 参数组合,多个分类不同组合列表
  8. 算法设计与分析——概述
  9. 深圳市各行政区域最新地图数据
  10. 东北大学《传输原理》随堂练习
  11. php curl发邮件,使用PHP cURL通过Mailgun API发送带附件的电子邮件
  12. java 红包算法_JAVA实现拼手气红包算法
  13. Unity优化篇——mesh合并
  14. HTML网页设计基础——用户注册界面
  15. Unity的gamma矫正、颜色空间及其转换的问题
  16. 干货 | 这可能是最详细的「阻抗匹配」介绍
  17. CA认证原理以及实现(上)
  18. 【SQL Server】列转行 STUFF 函数
  19. 从一路赞美到嘘声不断 90后创业热潮已宣告死亡
  20. 计算机学习常用网站总结

热门文章

  1. java argox_HTML+CSS3再加一点点JS做的一个小时钟
  2. 使用路由器搭建机器人局域网,进而远程机器人主机
  3. 肘关节附属运动测试软件,肘关节运动学(一)
  4. java中的4种访问制权限有哪些?分别作用范围是什么?
  5. HQL是什么HQL和SQL的区别
  6. Plist文件是什么?
  7. 〖Python接口自动化测试实战篇①〗- 自动化测试基础扫盲及项目的生命周期详述
  8. 第七章-复用类-继承语法-1
  9. spring框架学习 - Data Access之 事务管理 - 声明式事务管理
  10. Visual Studio调试方式详解