博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
poj1417(种类并查集+dp)
阅读量:5242 次
发布时间:2019-06-14

本文共 3573 字,大约阅读时间需要 11 分钟。

题目:

 

题意:输入三个数m, p, q 分别表示接下来的输入行数,天使数目,恶魔数目;

接下来m行输入形如x, y, ch,ch为yes表示x说y是天使,ch为no表示x说y不是天使(x, y为天使,恶魔的编号,1<=x,y<=p+q);天使只说真话,恶魔只说假话;

如果不能确定所有天使的编号,输出no,若能确定,输出所有天使的编号,并且以end结尾;

注意:可能会有连续两行一样的输入;还有,若x==y,x为天使;

 

思路:种类并查集+dp;

我们分析输入的数据不难发现,对于输入x, y, yes,假设x为天使,则y也为为天使,若x为恶魔,那么y也为恶魔,即x, y, 同为恶魔或者天使;

对于输入x, y, no,同理可得x, y, 一者为天使一者为恶魔;即可得ch为yes时,x, y, 属同种,ch为no时, x, y属异种;

那么我们很容易就能想到种类并查集,rank[x]表示x与其父亲节点的关系,rank[x]=0表示x与其父亲节点属于同类,rank[1]表示x与其父亲节点属于异类;通过并查集将能确定相对关系的编号放在一个集合里面,每个结合里面的编号可以分为两部分,和根节点属同种的的节点,以及和根节点属于异种的节点;这样并不能直接确定答案,我们确定了划分集合的个数以及每个集合里面和根节点同种的节点数目以及异节点的数目;从每个集合里面选择一种节点,若所有选中的节点数目和为p的选择方法唯一,那么我们能够确定所有天使的编号,反之则不能;关于这个问题我们可以用dp完美解决;

事实上这个题目前面的并查集部分只是一个普通的种类并查集,这个记录路径的dp才是本题解的精妙部分;我们先用一个tot变量来存储集合个数(对于x==y的情况,我们让x单独为一个集合),并且用gg数组来标记所有编号属于的集合;用tag数组存储每个集合两种种类的数目;dp[i][j]表示到第i个集合选择种类的和为j的方法总数,即dp[tot][p]==1时能确定答案;对于dp过程中的每个选择我们用jj数组记录,然后反推选择路径用cc数组记录路径就ok啦~

 

代码:

1 #include 
2 #include
3 #include
4 #define MAXN 600 5 using namespace std; 6 7 int pre[MAXN], rank[MAXN], gg[MAXN], jj[MAXN][MAXN], tag[MAXN][2], dp[MAXN][MAXN], cc[MAXN][2]; 8 9 int find(int x){10 if(x!=pre[x]){11 int px=find(pre[x]);12 rank[x]^=rank[pre[x]];13 pre[x]=px;14 }15 return pre[x];16 }17 18 void jion(int x, int y, int d){19 int px=find(x);20 int py=find(y);21 if(px!=py){22 pre[py]=px;23 rank[py]=rank[x]^rank[y]^d;24 }25 }26 27 int main(void){28 int m, p, q;29 while(scanf("%d%d%d", &m, &p, &q)){30 if(m+p+q==0){31 break;32 }33 for(int i=1; i<=p+q; i++){ //**初始话34 rank[i]=0;35 pre[i]=i;36 }37 while(m--){38 int x, y, d=1;39 char ch[5];40 scanf("%d%d%s", &x, &y, ch);41 if(ch[0]=='y'){42 d=0;43 }44 jion(x, y, d);45 }46 memset(gg, 0, sizeof(gg)); //**gg存储集合个数并且给他们编号47 memset(jj, 0, sizeof(jj));48 memset(tag, 0, sizeof(tag));49 memset(dp, 0, sizeof(dp));50 memset(cc, 0, sizeof(cc));51 int tot=0;52 for(int i=1; i<=p+q; i++){ //**统计集合个数并且编号53 if(find(i)==i){54 gg[i]=++tot;55 }56 }57 for(int i=1; i<=p+q; i++){ //**分别统计每个集合两种类的数目并存储到tag中58 tag[gg[find(i)]][rank[i]]++;59 }60 dp[0][0]=1;61 for(int i=1; i<=tot; i++){62 for(int j=0; j<=p+q; j++){ //**dp[i][j]存储到第i个集合选择种类和为j的方法数63 if(j-tag[i][0]>=0&&dp[i-1][j-tag[i][0]]){64 dp[i][j]+=dp[i-1][j-tag[i][0]];65 jj[i][j]=tag[i][0]; //**jj数组记录路径,即选的是1还是066 }67 if(j-tag[i][1]>=0&&dp[i-1][j-tag[i][1]]){68 dp[i][j]+=dp[i-1][j-tag[i][1]];69 jj[i][j]=tag[i][1];70 }71 }72 }73 if(dp[tot][p]!=1){74 printf("no\n");75 }else{76 for(int i=tot,j=p; j>0&&i>0; i--){ //**标记路径77 if(jj[i][j]==tag[i][0]){78 cc[i][0]=1;79 }else{80 cc[i][1]=1;81 }82 j-=jj[i][j];83 }84 for(int i=1; i<=p+q; i++){85 if(cc[gg[find(i)]][rank[i]]){86 printf("%d\n", i);87 }88 }89 printf("end\n");90 }91 }92 return 0;93 }

 

转载于:https://www.cnblogs.com/geloutingyu/p/6142473.html

你可能感兴趣的文章
CAS单点登录
查看>>
context-param 监听器 过滤器 servlet 拦截器的区别
查看>>
CAS之TICKET
查看>>
一个用消息队列 的人,不知道为啥用 MQ,这就有点尴尬
查看>>
jq实现瀑布流效果
查看>>
css3 3D盒子效果
查看>>
如何在VS2010(VC10)下编译Pro*C OCCI 程序
查看>>
3.随机生成4位验证码,由用户输入并验证是否输入正确,如果输入错误就生成新的验证码让用户重新输入,最多输入5次...
查看>>
练习2 练习目标-使用引用类型的成员变量:在本练习中,将扩展银行项目,添加一个(客户类)Customer类。Customer类将包含一个Account对象。...
查看>>
产品叠加搜索
查看>>
bzoj1072: [SCOI2007]排列perm
查看>>
字符串加密
查看>>
Windows10远程连接错误-出现身份验证错误
查看>>
[redis]redis常用
查看>>
凑凑热闹,给eval做个科普.
查看>>
【雷电】源代码分析(二)-- 进入游戏攻击
查看>>
POJ 1220 高精度/进制转换
查看>>
cocos2d-x中CCLabelAtlas的小图片拼接
查看>>
BuilderParttern(建造者模式)
查看>>
Codeforces Round #422 A
查看>>