首页 > 代码库 > POJ 1417 True Liars(并查集+DP)
POJ 1417 True Liars(并查集+DP)
大意:
一个岛上有神与恶魔两个种族,神会说真话,恶魔会说假话。已知神与恶魔的个数,但不知道具体个人是属于哪个。
n,x,y
这个人问n次 ,x为神的个数,y为恶魔的个数。
每次的问题为 xi,yi,a 问xi ,yi是否为神? a为yes/no。注意xi,yi可能为同一个人
若最终可得出哪些是神则从小到大输出神的编号,并最终输出end
否则输出no
思路:
经过简单推理可得,只要是说yes,xi,yi为同一个族,no则不为同一个族。
这样通过使用并查集+relation(relation[i]为i与父亲的关系)可得出
但是分为了几个不同的群,判断是否可以拼成神的个数。
使用DP,即背包问题。这里的状态定义为dp[i][j]前i个群中拼成j有几种情况。
若情况惟一则正确,否则no
注意:
/************************************************************************/ /* 提供测试数据:11 9 76 12 no4 8 yes3 12 yes5 9 no6 11 yes6 5 no15 4 yes16 15 yes10 3 no6 16 no2 6 no6 2 41 1 yes2 3 yes3 4 yes4 5 no5 6 yes6 6 yes3 3 21 2 no3 4 yes3 5 no0 0 0输出:no56endno */ /************************************************************************/ #include<math.h> #include<stdio.h> #include<string.h> #include<algorithm> #define maxn 700 using namespace std; int N,M,T,X,Y; int ans; int parent[maxn]; int relate[maxn]; char str[10]; int indata[2*maxn]; int dp[maxn][maxn]; int path[maxn][maxn]; int setname[maxn]; int find(int *parent,int k); int backpack() { int i,j,k,t,m; k=0; memset(dp,0,sizeof(dp)); memset(path,0,sizeof(path)); memset(indata,0,sizeof(indata)); memset(setname,0,sizeof(setname)); for(i=1;i<=X+Y;i++) find(parent,i);//再压缩一下路径 for(i=1;i<=X+Y;i++) if(parent[i]==-1) setname[k++]=i;//得到各个集合,以根结点标识 for(i=0;i<k;i++) for(j=indata[2*i]=1;j<=X+Y;j++)//<span style="color:#FF0000;">根与自身定为同类</span> if(parent[j]==setname[i]) if(relate[j]==0) indata[2*i]++;//统计与自己同类的元素个数 else indata[2*i+1]++; dp[0][indata[0]]++;path[0][indata[0]]=1; dp[0][indata[1]]++;path[0][indata[1]]=2; /*for(i=0;i<k;i++) printf("%d,%d\n",indata[i*2],indata[i*2+1]);*//*这里WA了好久,之前用无限背包的想法,dp设为一维数组。但是这里不同的是背包为两种重量而非之前选或不选。所以不能用。否则会计算重叠。这里要求是全部的集合都使用完后再判断(因为每个集合中都是与自己相反或相同的类别)。*/ for(i=1;i<k;i++) for(j=X;j>=0;j--) { if(j>=indata[2*i] && dp[i-1][j-indata[2*i]] ) dp[i][j]+=dp[i-1][j-indata[2*i]],path[i][j]=1; if(j>=indata[2*i+1] && dp[i-1][j-indata[2*i+1]] ) dp[i][j]+=dp[i-1][j-indata[2*i+1]],path[i][j]=2; } if(dp[k-1][X]!=1) return 0; ans=0; m=X; i=k-1; while(i>=0) { if(path[i][m]==1) t=0,m=m-indata[2*i]; else t=1,m=m-indata[2*i+1]; for(j=1;j<=X+Y;j++) if(parent[j]==setname[i] || j==setname[i]) if(relate[j]==t) dp[0][ans++]=j; i--; } sort(dp[0],dp[0]+ans); return 1; } void swap(int *a,int *b) { int t; t=*a; *a=*b; *b=t; } int find(int *parent,int k) { if(parent[k]==-1) return k; int t=parent[k]; parent[k]=find(parent,parent[k]); relate[k]=(relate[k]+relate[t])%2;//并查集的高级应用,即每次合并都得到该点与父节点的关系。 return parent[k]; } int main() { int i; int e,r,ee,rr,cnt; //freopen("input.txt","r",stdin); //freopen("output.txt","w+",stdout); while(scanf("%d%d%d",&N,&X,&Y)) { if(X==0 && Y==0 && N==0) break; memset(parent,-1,sizeof(parent)); memset(relate, 0,sizeof(relate)); for(i=0;i<N;i++) { scanf("%d%d%s",&e,&r,str); if(r<e)swap(&r,&e); ee=find(parent,e); rr=find(parent,r); if(ee==rr) continue;//<span style="color:#FF0000;">若属于同一个集合则不合并,否则会有环路产生RE</span> if(str[0]=='y') { parent[rr]=ee,relate[rr]=(relate[e]+relate[r])%2; } else parent[rr]=ee,relate[rr]=(1+relate[r]+relate[e])%2; } if(X==Y)//若X,Y个数相等则没有可能 { printf("no\n"); continue; } if(backpack()==0) printf("no\n"); else { for(i=0;i<ans;i++) printf("%d\n",dp[0][i]); printf("end\n"); } } return 0; }
POJ 1417 True Liars(并查集+DP)
声明:以上内容来自用户投稿及互联网公开渠道收集整理发布,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任,若内容有误或涉及侵权可进行投诉: 投诉/举报 工作人员会在5个工作日内联系你,一经查实,本站将立刻删除涉嫌侵权内容。