题意:
正义阵营只会说真话,邪恶阵营只会说假话
共有n句话,形如x y a
表示:x说y属于正义或邪恶(x可以等于y)
其中a为yes或no(yes表示正义,no表示邪恶)
题目保证不会出现矛盾的情况。
分析:
①如果a是yes,那么x y一定是同一个阵营,如果是no,一定是相反阵营
②据此我们可以用带权并查集维护1~p1+p2所有节点之间的相对关系,
0表示同阵营,1表示相反阵营。
最终维护得到若干个连通块。每个连通块分别有若干个正义/邪恶阵营,统计信息,存进数组bag[i][0] / bag[i][1] 当中 。同时给每个连通块标号
(表示以第i个连通块中,包括根节点在内和根节点同阵营的节点有bag[i][0]个,和根节点不同阵营的有bag[i][1]个)
③到这里问题就简化成:对于每个连通块,有2个阵营进行二选一,问所有的连通块选一遍之后,得到p1人数有多少种选法,如果只有1种,那么就有解,否则答案是no。
到这里就是一个朴素的01背包问题了,dp[i]j[j]表示前i个连通块选到j个人的选法个数。 转移方程如下:
最后再逆序判断对于每一个连通块,最优解是选择了哪一个阵营。细节见代码。
#include<iostream>
#include<cstdio>
#include<set>
#include<vector>
#include<map>
#include<algorithm>
#include<string.h>
using namespace std;
#define ll long long
#define ull unsigned long long
const int maxn = 8e2+7;
const ll inf = 34359738370;
const int has = 99959;
//p1 p2 2个阵营 p1说真话 p2说假话
//a b yes/no 表示a说b是p1/p2
//判断能否找出哪些说真话并输出
//分析:yes表示a b同阵营 no表示a b反阵营 用带权并查集最终处理得到若干个连通块 每个连通块有2个阵营
//01背包 dp[i][j]表示前i个连通块选中j个人的方案数
int fa[maxn],v[maxn];//v[i]==0 表示i 和fa[i]同阵营 1 反
int dp[maxn][305];//前i个连通块 选到j个人的方案数
int p1,p2,m;
int bag[maxn][2];//第i个连通块 第0/1阵营的人数 0表示与根节点同阵营
int num[maxn];//以i为根的连通块的编号
int cnt=0;
inline int find(int x)
{
if(fa[x] == x) return x;
int f=fa[x];
fa[x]=find(fa[x]);
v[x]=(v[x]+v[f])%2;
return fa[x];
}
inline void hb(int x,int y,int k)
{
int r1=find(x),r2=find(y);
if(x == y) return ;
fa[r1]=r2;
v[r1]=(v[y]+k-v[x]+2)%2;
return ;
}
void init()
{
for(int i=0;i<maxn;i++) fa[i]=i;
memset(dp,0,sizeof(dp));
memset(v,0,sizeof(v));
memset(num,0,sizeof(num));
memset(bag,0,sizeof(bag));
cnt=0;
}
void get_cnt()
{
for(int i=1;i<=p1+p2;i++)
{
int f=find(i);
if(!num[f]) num[f]=++cnt;
bag[num[f]][v[i]]++;
}
}
void dp_solve()
{
dp[1][bag[1][0]]++;
dp[1][bag[1][1]]++;//不能赋值为1 因为可能bag[1][0]==bag[1][1]
for(int i=2;i<=cnt;i++)
{
//第i个阵营只能选0 / 1 两种情况
for(int j=0;j<=p1;j++)
{
if(j>=bag[i][0]) dp[i][j]=dp[i-1][j-bag[i][0]];//第i个阵营选0
if(j>=bag[i][1]) dp[i][j]+=dp[i-1][j-bag[i][1]];//第i个阵营选1
}
}
}
int main()
{
while(~scanf("%d %d %d",&m,&p1,&p2) && m+p1+p2)
{
init();
char s[10];
while(m--)
{
int a,b;
scanf("%d %d %s",&a,&b,s);
if(s[0]=='n') hb(a,b,1);
else hb(a,b,0);
}
//得到若干个连通块
get_cnt();
dp_solve();//01背包
if(dp[cnt][p1] == 1)//方案唯一 可以知道p1是哪些
{
//先逆推得到每个连通块是选了哪个阵营
int chos[maxn],j=p1;
fill(chos,chos+maxn,-1);
for(int i=cnt;i>1;i--)//i==1的时候 j就是bag[1][0/1]的数目
{
if(1 == dp[i-1][j-bag[i][0]]) //逆推过程中都是等于1
{
chos[i]=0;
j-=bag[i][0];
}
else //如果不是取的0 那就是取的1
{
chos[i]=1;
j-=bag[i][1];
}
}
if(j == bag[1][0]) chos[1]=0;
else chos[1]=1;
for(int i=1;i<=p1+p2;i++)
{
int f=find(i);
if(chos[num[f]]==v[i]) printf("%d\n",i);
}
printf("end\n");
}
else printf("no\n");
}
return 0;
}