poj1417(带权并查集+背包DP+路径回溯)

题目链接: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 }

猜你喜欢

转载自www.cnblogs.com/FrankChen831X/p/10466608.html