True Liars(带权并查集+dp)

2023-10-31 18:41
文章标签 dp 查集 true 带权 liars

本文主要是介绍True Liars(带权并查集+dp),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

题目链接:https://vjudge.net/problem/POJ-1417

题目大意:世界上有两种人,一种是好人,他们说的每句话都是实话;另一种人是坏人,他们说的每句话都是假话。你可以问他们别人(包括他自己)是不是好人,他们会给你"yes"或者"no"的回答。现在给出n次提问,并且告诉你了好人和坏人人数,问你是不是有且只有一种合法的情况。如果是,就顺序输出好人;如果不是就输出"no" 

​​​​​​​思路参考:True Liars (POJ - 1417)带权并查集+dp路径_AC__dream的博客-CSDN博客

思路:

        我们对于他们的回答来分析一下,假设你问A:B是不是好人:如果A的回答是"yes",那么又可以分为两种情况:1.A是好人:那么B也是好人;2.A是坏人,那么B也是坏人。如果A的回答是“no”,同样存在两种情况:1.A是好人,那么B就是坏人;2.A是坏人,那么B就是好人。

        我们可以发现:如果回答的是"yes",那么不论是不是好人,首先A和B必是同一种人;如果回答是“no”,那么A和B必是不同种的人

        那么这就非常类似与带权并查集的处理方式,我们用节点与根节点的距离来代表两者之间的关系,我们假设:如果与根节点的距离是0,那么这个节点和根节点是一种人;如果与根节点的距离是1,那么这个节点和根节点就不是一种人

        那么就会涉及到路径压缩的问题,当我每次merge的时候应该如何更新旧爸爸到新爸爸之间的距离并且不改变代表的含义呢,如下图所示,要将x和ymerge起来,我们要新处理的边是dis:

 我们就可以得到压缩路径的公式为dis=(d[x] + op - d[y] + 2) % 2,加上的2是一个偏移量为了保证括号内的数为正数。

        那么此时问题就简化为:有若干个集合,每个集合中有两种人,分别从每个集合中选一部分人作为好人,看有没有唯一的一种方式使得好人数是p1的问题

        例如:集合一有两种人,人数分别为:2,3;集合二有两种人:人数分别为1,5。现在告诉你总共有4个好人,那么显然只能选择集合一中的3和集合二中的1。

        此时问题就是一个背包问题,我们用dp[i][j]来代表从前i个集合中选取j个人作为好人的方案总数。同时如果存在解的情况下我们还要输出具体是哪些人,所以我们还要知道每个集合中两种人具体是哪些人,具体实现代码如下:

#include <cstdio>
#include <algorithm>
#include <cstring>
#include <iostream>
#include <vector>
#define pb(x) push_back(x) 
using namespace std ; 
const int N = 1008 ; int prv[N] ;        // father 
int d[N] ;          //到根节点的距离
bool vis[N] ;       //标记这个点有没有用过,用来统计一个集合中有多少元素用的
int cnt[N][2] ;     //cnt[i][0] / cnt[i][1] 分别代表第i个集合中两部分人的个数
std::vector<int> s[N][2] ;  //记录第i个集合中0部分具体是哪些人,1部分具体是哪些人
int dp[N][N] ;      //dp[i][j] 代表从前i个集合中选j个人是好人的方案数
int path[N][N] ;    //path[i][j] 用来记录路径,记录i,j这个状态是由哪个组合传递过来的
int n , p , q ; int find(int x){if(x != prv[x]){int t = prv[x] ;    //带权并查集压缩路径这里一定要先获取fatherprv[x] = find(t) ;  d[x] = (d[x] + d[t]) % 2 ; //压缩路径 }return prv[x] ; 
}void merge(int x , int y , int op){int fx = find(x) , fy = find(y) ; if(fx != fy){prv[fy] = fx ; d[fy] = (d[x] + op - d[y] + 2) % 2 ;    //也是压缩路径,但是要加偏移量保证括号内是正数}
}void init(){for(int i = 1 ; i <= p + q ; i ++){prv[i] = i ; vis[i] = false ; d[i] = cnt[i][0] = cnt[i][1] = 0 ; s[i][0].clear() , s[i][1].clear() ; }
}int main(){while(cin >> n >> p >> q){if(n == 0 && p == 0 && q == 0) break ; init() ; while(n --){int a , b ; string op ; cin >> a >> b >> op ; if(op == "yes"){   //同类merge(a , b , 0) ; }else{merge(a , b , 1) ; }}int count = 0 ; for(int i = 1 ; i <= p + q ; i ++){if(!vis[i]){int t = find(i) ; for(int j = i ; j <= p + q ; j ++){if(t == find(j)){cnt[count + 1][d[j]] ++ ; s[count + 1][d[j]].pb(j) ; vis[j] = true ; }}count ++ ; }}memset(dp , 0 , sizeof dp) ; dp[0][0] = 1 ; for(int i = 1 ; i <= count ; i ++){for(int j = p ; j >= 0 ; j --){if(j >= cnt[i][0] && dp[i - 1][j - cnt[i][0]]){  //如果能选第i个集合的0部分人dp[i][j] += dp[i - 1][j - cnt[i][0]] ; path[i][j] = cnt[i][0] ; }if(j >= cnt[i][1] && dp[i - 1][j - cnt[i][1]]){  //如果能选第i个集合的1部分人dp[i][j] += dp[i - 1][j - cnt[i][1]] ; path[i][j] = cnt[i][1] ; }}}if(dp[count][p] != 1) cout << "no" << '\n' ; else{vector<int>ans ; ans.clear() ; while(count){if(path[count][p] == cnt[count][0]){        //寻找路径p -= cnt[count][0] ; for(int i = 0 ; i < cnt[count][0] ; i ++){ans.pb(s[count][0][i]) ; }}else{p -= cnt[count][1] ; for(int i = 0 ; i < cnt[count][1] ; i ++){ans.pb(s[count][1][i]) ; }}count -- ; }sort(ans.begin() , ans.end()) ; for(int i = 0 ; i < ans.size() ; i ++){cout << ans[i] << '\n' ; }cout << "end" << '\n' ; }}return 0 ; 
}

[水平有限,欢迎指正]

这篇关于True Liars(带权并查集+dp)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/317337

相关文章

hdu4826(三维DP)

这是一个百度之星的资格赛第四题 题目链接:http://acm.hdu.edu.cn/contests/contest_showproblem.php?pid=1004&cid=500 题意:从左上角的点到右上角的点,每个点只能走一遍,走的方向有三个:向上,向下,向右,求最大值。 咋一看像搜索题,先暴搜,TLE,然后剪枝,还是TLE.然后我就改方法,用DP来做,这题和普通dp相比,多个个向上

hdu1011(背包树形DP)

没有完全理解这题, m个人,攻打一个map,map的入口是1,在攻打某个结点之前要先攻打其他一个结点 dp[i][j]表示m个人攻打以第i个结点为根节点的子树得到的最优解 状态转移dp[i][ j ] = max(dp[i][j], dp[i][k]+dp[t][j-k]),其中t是i结点的子节点 代码如下: #include<iostream>#include<algorithm

hdu4865(概率DP)

题意:已知前一天和今天的天气概率,某天的天气概率和叶子的潮湿程度的概率,n天叶子的湿度,求n天最有可能的天气情况。 思路:概率DP,dp[i][j]表示第i天天气为j的概率,状态转移如下:dp[i][j] = max(dp[i][j, dp[i-1][k]*table2[k][j]*table1[j][col] )  代码如下: #include <stdio.h>#include

usaco 1.1 Broken Necklace(DP)

直接上代码 接触的第一道dp ps.大概的思路就是 先从左往右用一个数组在每个点记下蓝或黑的个数 再从右到左算一遍 最后取出最大的即可 核心语句在于: 如果 str[i] = 'r'  ,   rl[i]=rl[i-1]+1, bl[i]=0 如果 str[i] = 'b' ,  bl[i]=bl[i-1]+1, rl[i]=0 如果 str[i] = 'w',  bl[i]=b

uva 10154 DP 叠乌龟

题意: 给你几只乌龟,每只乌龟有自身的重量和力量。 每只乌龟的力量可以承受自身体重和在其上的几只乌龟的体重和内。 问最多能叠放几只乌龟。 解析: 先将乌龟按力量从小到大排列。 然后dp的时候从前往后叠,状态转移方程: dp[i][j] = dp[i - 1][j];if (dp[i - 1][j - 1] != inf && dp[i - 1][j - 1] <= t[i]

uva 10118 dP

题意: 给4列篮子,每次从某一列开始无放回拿蜡烛放入篮子里,并且篮子最多只能放5支蜡烛,数字代表蜡烛的颜色。 当拿出当前颜色的蜡烛在篮子里存在时,猪脚可以把蜡烛带回家。 问最多拿多少只蜡烛。 代码: #include <iostream>#include <cstdio>#include <cstdlib>#include <algorithm>#include <cs

uva 10069 DP + 大数加法

代码: #include <iostream>#include <cstdio>#include <cstdlib>#include <algorithm>#include <cstring>#include <cmath>#include <stack>#include <vector>#include <queue>#include <map>#include <cl

uva 10029 HASH + DP

题意: 给一个字典,里面有好多单词。单词可以由增加、删除、变换,变成另一个单词,问能变换的最长单词长度。 解析: HASH+dp 代码: #include <iostream>#include <cstdio>#include <cstdlib>#include <algorithm>#include <cstring>#include <cmath>#inc

XTU 1233 n个硬币连续m个正面个数(dp)

题面: Coins Problem Description: Duoxida buys a bottle of MaiDong from a vending machine and the machine give her n coins back. She places them in a line randomly showing head face or tail face o

poj 1182 并查集 食物链类

题意: 有n只动物,分别编号1....n。所有动物都属于A,B,C中的一种,已知A吃B,B吃C,C吃A。 按顺序给出下面两种共K条信息: 1. x 和 y 属于同一类。 2. x 吃 y 。 然而这些信息可能会出错,有可能有的信息和之前给出的信息矛盾,也有的信息可能给出的 x 和 y 不在n的范围内。 求k条信息中有多少条是不正确的。 解析: 对于每只动物,创建3个元素 i