题目链接:http://poj.org/problem;jsessionid=8C1721AF1C7E94E125535692CDB6216C?id=1417
题意:有p1个天使,p2个恶魔,天使只说真话,恶魔只说假话。问n句话,问x:y是否为天使,x回答yes或no,分别表示是或否,问能否确认为天使的人的编号(1..p1+p2),若能按顺序1输出,否则输出no。
思路:
看到带权并查集的题首先考虑到向量,先分析一下,不访设x->y表示x说y是什么,0表示天使,1表示是恶魔,枚举一下会发现x->y=0也表示x y同类,=1表示x y异类。并且x->z=(x->y)^(y->z)。这样就很清晰了,我们使用并查集将有关系的人并起来,并得到每个人与其祖先的关系,这样之后就会得到一些集合,每个集合有两类人。我想到这了就不知道怎么做了,因为我是在刷bin巨并查集专题看到的这题,我的潜意识就是怎么用并查集去解决这个问题,所以我一直在想是不是有其他的并查集的方法。看了别人的博客才恍然大悟之后就是一个完全背包的题了啊。还是太年轻了,思维应该开阔些,不能局限在一种思维上去想怎么做题。
回到题目,利用并查集得到这些集合比较简单,稍微熟悉并查集都能想到,接下来的背包DP和路径回溯才是这道题的核心。先遍历一遍用r[i]表示第i个集合的祖先,用a[i][0]表示第i个集合与祖先同类的人数,用a[i][1]表示第i个集合与祖先异类的人数。之后就是dp部分,dp[i][j]表示前i个集合中天使个数为j的方法数,按背包模板来就行。当dp[tot][p1]==1时有解,否则输出“no"。若有解还需要输出编号,只需要从dptot][p1]往前回溯即可,因为要按升序,需要sort一下。至此这道题才算结束,但因为敲错了一个变量名,我找了一个小时bug,写代码时还是要心细,不然太折磨人了。
代码如下:
1 #include<cstdio>
2 #include<cstring>
3 #include<algorithm>
4 using namespace std;
5
6 int n,p1,p2,x,y,d;
7 int root[605],f[605],dp[605][305],r[605],a[605][2],res[305];
8
9 int getr(int k){
10 if(root[k]==k) return k;
11 else{
12 int tmp=root[k];
13 root[k]=getr(root[k]);
14 f[k]^=f[tmp];
15 return root[k];
16 }
17 }
18
19 int main(){
20 while(scanf("%d%d%d",&n,&p1,&p2),n||p1||p2){
21 for(int i=1;i<=p1+p2;++i)
22 root[i]=i,f[i]=0,a[i][0]=a[i][1]=0;
23 while(n--){
24 char ch[5];
25 scanf("%d%d%s",&x,&y,ch);
26 if(ch[0]=='y') d=0;
27 else d=1;
28 int rx=getr(x),ry=getr(y);
29 if(rx!=ry){
30 root[ry]=rx;
31 f[ry]=f[x]^f[y]^d;
32 }
33 }
34 int tot=0;
35 memset(dp,0,sizeof(dp));
36 for(int i=1;i<=p1+p2;++i){
37 if(i==getr(i)){
38 r[++tot]=i;
39 for(int j=1;j<=p1+p2;++j)
40 if(getr(j)==i)
41 if(f[j]==0) a[tot][0]++;
42 else a[tot][1]++;
43 }
44 }
45 dp[0][0]=1;
46 for(int i=1;i<=tot;++i){
47 for(int j=p1;j>=a[i][0];--j)
48 dp[i][j]+=dp[i-1][j-a[i][0]];
49 for(int j=p1;j>=a[i][1];--j)
50 dp[i][j]+=dp[i-1][j-a[i][1]];
51 }
52 if(dp[tot][p1]!=1){
53 printf("no\n");
54 continue;
55 }
56 int p=p1,num=0;
57 for(int i=tot;i>=1;--i)
58 if(dp[i-1][p-a[i][0]]==1){
59 for(int j=1;j<=p1+p2;++j)
60 if(getr(j)==r[i]&&f[j]==0)
61 res[num++]=j;
62 p-=a[i][0];
63 }
64 else{
65 for(int j=1;j<=p1+p2;++j)
66 if(getr(j)==r[i]&&f[j]==1)
67 res[num++]=j;
68 p-=a[i][1];
69 }
70 sort(res,res+num);
71 for(int i=0;i<num;++i)
72 printf("%d\n",res[i]);
73 printf("end\n");
74 }
75 return 0;
76 }
来源:oschina
链接:https://my.oschina.net/u/4331678/blog/3629069