poj-1417 ❤️ 并查集01模型+背包dp
题意:
有n次对话,p1个好人,p2个坏人,问x2是一个好人吗,x1会回答yes或者no,如果x1是一个坏人他会说谎话,如果它是好人,他会说真话。
如果没有确切的证据找出这p1和p2个人,那么就写no。否则就按大小顺序输出。
题解:
由上分析知道说yes时两个人同属性,说no时不同属性。
故而做成并查集,有相关性的人会被放到同个集合内,而集合内有2种属性,即是否和根节点相同属性。
这时候就转化成另一个问题了,有(p1+p2)个数,其中有p1个A和p2个B,每组数里面有2种属性的人,各知道人数,但不知道是A还是B。这个问题用背包可以解决。
#include "cstdio"
#include "cstring"
#include "vector"
#include "algorithm"
using namespace std;
const int maxn=609;
int n,p1,p2,fa[maxn],rak[maxn];
int anc(int a)
{
if(fa[a]==a)return a;
int k=fa[a];
fa[a]=anc(fa[a]);
rak[a]=(rak[k]+rak[a]+2)%2;
return fa[a];
}
void connect(int a,int b,int c)
{
int fa_a=anc(a); //tle 的原因:这里写成fa[a]了。
int fa_b=anc(b);
if(fa_a!=fa_b) {
fa[fa_b] = fa_a;
rak[fa_b]=(rak[a]-rak[b]+c+2)%2;
}
}
void init(int n)
{
for(int i=1;i<=n;++i)
{
fa[i]=i;
rak[i]=0;
}
}
int dp[maxn][maxn],step[maxn][maxn];
int main()
{
// freopen("in.text","r",stdin);
while (scanf("%d %d %d",&n,&p1,&p2))
{
if(n==0&&p1==0&&p2==0)break;
init(p1+p2);
//把元素都分好集合
for(int i=1;i<=n;++i)
{
int a,b,c;
char cc[4];
scanf("%d %d %s",&a,&b,cc);
if(cc[0]=='y')c=0; else c=1;
connect(a,b,c);
}
int cnt=0; //集合数
int party[maxn]; //记录每个集合的根节点
int party_man[maxn][2]; //记录每个集合的人数
//算清每个集合里的A和B的数量
for(int i=1;i<=p1+p2;++i)
{
int d,fa_i=anc(i);
for( d=1;d<=cnt;++d)
{
if(fa_i==party[d]){
party_man[d][rak[i]]++;
break;
}
}
if(d==cnt+1)
{
party[++cnt]=fa_i;
party_man[cnt][0]=0;
party_man[cnt][1]=0;
party_man[cnt][rak[i]]=1;
}
}
//用背包的方法算清如何分配正确。
memset(dp,0,sizeof(dp));
memset(step,0,sizeof(step)); //记录步骤
dp[0][0]=1;
for(int d=0;d<=p1;++d)
for(int i=1;i<=cnt;++i)
{
for(int k=0;k<=1;++k) {
int now = party_man[i][k];
if (d-now>=0&&dp[i - 1][d - now] > 0)
{
dp[i][d] += dp[i - 1][d-now];
step[i][d]=now;
}
}
}
if(dp[cnt][p1]!=1){printf("no\n"); continue;} //注意此处
vector<int>finalans;
int xx=cnt,yy=p1;
for(int i=cnt;i>=1;--i)
{
int now=step[i][yy]; yy-=now;
if(party_man[i][1]!=now&&party_man[i][0]!=now)continue;
int aa=0;
if(party_man[i][1]==now)aa=1;
for(int d=1;d<=p1+p2;++d)
//wa 的原因:这里p1+p2写成n了
{
if(anc(d)==party[i]&& rak[d]==aa)
finalans.push_back(d);
}
}
sort(finalans.begin(),finalans.end());
for(int i=0;i<finalans.size();++i)
printf("%d\n",finalans[i]);
printf("end\n");
}
return 0;
}