题目链接: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 "); 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 ",res[i]); 73 printf("end "); 74 } 75 return 0; 76 }