poj1417(种类并查集+dp)

题目:http://poj.org/problem?id=1417

 

题意:输入三个数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 <iostream>
 2 #include <stdio.h>
 3 #include <string.h>
 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还是0
66                 }
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 }

 

posted @ 2016-12-07 21:00  geloutingyu  阅读(1904)  评论(4编辑  收藏  举报