P10687 True Liars 个人题解
题目大意:
给你两个神圣种族和邪恶种族的人数以及询问次数,其中神圣种族的人说真话,邪恶种族的人说假话,请你判断那几个是神圣种族的人。
Solution:
题解区已经有很多带边权并查集的做法了,这里我用的是扩展域并查集。
首先我们来看,询问时是有逻辑的,分为三种情况:
- 当 \(x\) 说 \(y\) 是神圣种族时,因为当 \(x\) 为邪恶种族时会说假话,则 \(y\) 也是邪恶种族;当 \(x\) 为神圣种族时会说真话,则 \(y\) 也是神圣种族,所以此时 \(x\) 与 \(y\) 同一种族。
- 当 \(x\) 说 \(y\) 是邪恶种族时,因为当 \(x\) 为邪恶种族时会说假话,则 \(y\) 是神圣种族;当 \(x\) 为神圣种族时会说真话,则 \(y\) 是邪恶种族,所以此时 \(x\) 与 \(y\) 不是同一种族。
- 当 \(x\) 说 \(x\) 是神圣种族时,因为无论 \(x\) 是神圣种族还是邪恶种族,都会说自己是神圣种族的,此时该询问无意义,可以跳过。
然后我们根据结论对并查集设置两个域,第一个域为同族域(\(1\) 到 \(p1+p2\)),第二个域为异族域(\(p1+p2+1\) 到 \(2(p1+p2)\)),我们每读进来一个就把其同族异族关系通过并查集合并,然后得到每个人的同族和异族。然后我们需要确定到底那些是神圣种族哪些是邪恶种族,可以发现:第 \(i\) 个人所在的集合是神圣种族时,与 \(i+p1+p2\) 处于一个集合的都是邪恶种族,即 \(fa_{j}(1\le i\le p1+p2,j\neq i)=fa_{i}\) 的都是神圣种族,\(fa_{j}(p1+p2+1\le i\le 2(p1+p2),j\neq i+p1+p2)=fa_{i+p1+p2}\) 的都是邪恶种族,而当第 \(i\) 个人所在集合是邪恶种族时同理。所以这个集合是什么种族是由其 \(fa\) 时好时坏决定的,我们用一个 \(a\) 数组表示集合的 \(fa\) 是神圣种族时神圣种族人数,用 \(b\) 数组表示集合的 \(fa\) 是邪恶种族时神圣种族人数。通过这个我们可以用一个背包 DP 来解决神圣种族有哪些人。
首先我们定义 \(dp_{i,j}\) 表示满足前 \(i\) 个集合有 \(j\) 个好人的方法数,转移方程就为:
\(dp_{i,j}=dp_{i,j}+\begin{cases}dp_{i-1,j-a_{i}} & a_{i}\le j\\dp_{i-1,j-b_{i}} & b_{i}\le j\end{cases}\) 初始状态为 \(dp_{0,0}=1\) 表示没有人时也算一种方案,最终状态为 \(dp_{cnt,p1}=1\),其中 \(cnt\) 为划分的集合数,如果最终状态不等于 \(1\),那么有多种方案,此时无解;否则我们就可以回溯找 \(dp_{i,j}\) 在何时发生转移的,并标记下此时的神圣种族最后枚举一遍输出就行(此时保证字典序输出)
代码:
#include<bits/stdc++.h>
using namespace std;
const int N=1500;
inline int read(){
int x=0,f=1;
char c=getchar();
while(c<'0' || c>'9'){
if(c=='-')
f=-1;
c=getchar();
}
while(c>='0' && c<='9'){
x=x*10+c-'0';
c=getchar();
}
return x*f;
}
int n,p1,p2,cnt,fa[N],dp[N][N],h[N],s[N],st[N],a[N],b[N];
//cnt表示集合数量,fa[i]表示i的祖先,dp[i][j]表示前i个集合有j个神圣种族的方法数
//h[i]是在按秩合并是判断大小所用的,s[i]表示i所在集合的大小,st[i]表示i所在的集合
//a[i]表示fa[i]是神圣种族时神圣种族人数,b[i]表示fa[i]是邪恶种族时神圣种族人数
inline int sfind(int x){
if(x==fa[x])
return x;
return fa[x]=sfind(fa[x]);
}
inline void merge(int x,int y){//并查集->按秩合并
int fx=sfind(x),fy=sfind(y);
if(fx==fy)
return;
if(h[fx]>h[fy])
s[fx]+=s[fy],fa[fy]=fx;
else if(h[fx]<h[fy])
s[fy]+=s[fx],fa[fx]=fy;
else
s[fx]+=s[fy],fa[fy]=fx,h[fx]++;
}
int main(){
while(n=read(),p1=read(),p2=read()){
if(!n && !p1 && !p2)
break;
int m=p1+p2;
for(int i=1;i<=m;i++){
fa[i]=i,fa[i+m]=i+m;
s[i]=h[i]=h[i+m]=1;
s[i+m]=st[i]=st[i+m]=0;
}
for(int i=1;i<=n;i++){
int x=read(),y=read();
string c;
cin>>c;
if(c=="no")
merge(x,y+m),merge(x+m,y);
else
merge(x,y),merge(x+m,y+m);
}
memset(dp,0,sizeof(dp));
dp[0][0]=1;
cnt=0;
for(int i=1;i<=m;i++){
int father=sfind(i);
if(father!=i)
continue;
st[father]=st[father+m]=++cnt;
a[cnt]=s[father];
b[cnt]=s[father+m];
}
for(int i=1;i<=cnt;i++){//枚举集合
for(int j=min(a[i],b[i]);j<=p1;j++){//枚举神圣种族的人数
if(j>=a[i])
dp[i][j]+=dp[i-1][j-a[i]];
if(j>=b[i])
dp[i][j]+=dp[i-1][j-b[i]];
}
}
if(dp[cnt][p1]!=1){//如果方案数不唯一退出
puts("no");
continue;
}
for(int i=cnt;i;i--){//回溯寻找什么时候发生了转移
if(dp[i-1][p1-a[i]])
p1-=a[i],a[i]=-1;
if(dp[i-1][p1-b[i]])
p1-=b[i],a[i]=-2;
}
for(int i=1;i<=m;i++){//枚举所有人找到神圣种族的人
int father=sfind(i);
if(st[father]){
if((father>m && a[st[father]]==-2) || (father<=m && a[st[father]]==-1))
printf("%d\n",i);
}
}
puts("end");
}
return 0;
}

浙公网安备 33010602011771号