题目链接
题意
岛上有说真话的好人和说假话的坏人,给你这两种人的人数。再给出q次问答结果,问答的格式是向a询问b是否是好人,回答是yes或者no。问是否可以分辨出全部好人,是的话打印清单
思路
网上的题解大多都是用带权并查集,统计当前节点和父节点是否同类。我自己第一次写用的是拓展并查集,也就是这篇题解,后来发现带权并查集似乎更好理解一些,如果想看带权并查集请戳这里。
这里用的是拓展并查集,开两倍空间,1-n(总人数)为每个人自己,n—2n为和自己相反属性的“自己”。分析一下询问,当回答yes时,a和b一定同为好人/坏人,a+n和b+n同理。那么unite(a,b),unite(a+n,b+n)。如果回答no,ab一定分别是好人和坏人,那么就unite(a,b+n),unite(b,a+n)。第一步处理就完成了
第一步完成后,我们拥有了并查集管理的多个独立块,在每一大块中,都可以根据1-n和n-2n分出两个小块,这两个小块中元素属性不同。需要注意,因为我们用的拓展并查集,这些大块中其实有一半是重复的。比如样例三中,最终分成四大块:1-2-10, 3-8-9, 4-5-6-14, 7-11-12-13。第1和第2块以及第3和第4块统计的是一样的信息(比如第一块信息是1号和2号属性相同,他们和3号属性不同,3-8-9也是一样的)。
在去重后,我们得到了反应属性关系的若干集合,每个集合再划分为两个子集。如果我们能推出谁是好人,那么好人的方案一定是唯一的。具体操作是我们从每个集合中选取一个集合作为好人,最终好人数量为给定的好人人数,且方案唯一,这就用到01背包了。
代码
#include<iostream>
#include<vector>
#include<cstring>
#define IOS ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
#define endl "\n"
using namespace std;
typedef long long ll;
const int maxn=2050;
const int inf=0x3f3f3f3f;
int fa[maxn];//父节点
int q,n,m;
int dp[1050][1050];//dp数组
bool ans[1050];//打印答案
bool b[1050];//集合去重
vector<int>v[maxn]; //暂时记录不同集合包含的点
vector<int>v1[maxn];//v1v2存放每个集合的子集
vector<int>v2[maxn];//
void init(){
for(int i=0;i<maxn;i++){
fa[i]=i;
}
memset(dp,0,sizeof(dp));
memset(b,0,sizeof(b));
memset(ans,0,sizeof(ans));
for(int i=0;i<maxn;i++){
v[i].clear();v1[i].clear();v2[i].clear();
}
}
int find(int x){
if(fa[x]==x)
return x;
else
return fa[x]=find(fa[x]);
}
void unite(int x,int y){
int xr=find(x),yr=find(y);
if(xr==yr){
return ;
}
fa[xr]=yr;
}
int main(){
IOS
while(cin>>q>>n>>m){
if(!q&&!n&&!m)
break;
init();
int sm=n+m;
while(q--){
int a,b;
string s;
cin>>a>>b>>s;
if(s=="yes"){
unite(a,b);
unite(a+sm,b+sm);
}
else{
unite(a,b+sm);
unite(a+sm,b);
}
}
if(n==m){
cout<<"no"<<endl;
continue;
}
for(int i=1;i<=2*sm;i++){
int x=find(i);
v[x].push_back(i);
}
int t=1;
for(int i=1;i<=2*sm;i++){
bool bl=0;
int le=v[i].size();
for(int j=0;j<le;j++){
int tmp=v[i][j];
if(tmp<=sm){
if(b[tmp])
continue;
v1[t].push_back(tmp);
b[tmp]=1;
bl=1;
}
else{
if(b[tmp-sm])
continue;
v2[t].push_back(tmp-sm);
b[tmp-sm]=1;
bl=1;
}
}
if(bl)
t++;
}
//dp部分
dp[0][0]=1;
for(int i=1;i<t;i++){
int s1=v1[i].size(),s2=v2[i].size();
for(int j=n;j>=s1;j--)
dp[i][j]+=dp[i-1][j-s1];
for(int j=n;j>=s2;j--)
dp[i][j]+=dp[i-1][j-s2];
}
if(dp[t-1][n]!=1)
cout<<"no"<<endl;
else{
int tmp=n;
for(int i=t-1;i>=1;i--){
int s1=v1[i].size(),s2=v2[i].size();
if(dp[i-1][tmp-s1]==1){
for(int j=0;j<s1;j++)
ans[v1[i][j]]=1;
tmp-=s1;
}
else{
for(int j=0;j<s2;j++)
ans[v2[i][j]]=1;
tmp-=s2;
}
}
for(int i=1;i<=sm;i++){
if(!ans[i])
continue;
cout<<i<<endl;
}
cout<<"end"<<endl;
}
}
return 0;
}