题意
【题意】
一个岛上存在着两种居民,一种是天神,一种是恶魔。
天神永远都不会说假话,而恶魔永远都不会说真话。
岛上的每一个成员都有一个整数编号(类似于身份证号,用以区分每个成员)。
现在你拥有n次提问的机会,但是问题的内容只能是向其中一个居民询问另一个居民是否是天神,请你根据收集的回答判断各个居民的身份。
【输入格式】
输入包含多组测试用例。
每组测试用例的第一行包含三个非负整数n,p1,p2,其中n是你可以提问的总次数,p1是天神的总数量,p2是恶魔的总数量。
接下来n行每行包含两个整数xi,yi以及一个字符串ai,其中xi,yi是岛上居民的编号,你将向编号为xi的居民询问编号为yi的居民是否是天神,
ai是他的回答,如果ai为“yes”,表示他回答你“是”,如果ai为“no”,表示他回答你“不是”。
xi,yi可能相同,表示你问的是那个人自己是否为天神。
当输入为占据一行的“0 0 0”时,表示输入终止。
【输出格式】
对于每组测试用例,如果询问得到的信息足以使你判断每个居民的身份,则将所有天神的编号输出,每个编号占一行,在输出结束后,在另起一行输出“end”,表示该用例输出结束。
如果得到的信息不足以判断每个居民的身份,则输出“no”,输出同样占一行。
【数据范围】
1≤xi,yi≤p1+p2,
1≤n<1000,1≤p1,p2<300。
【输入样例】
2 1 1
1 2 no
2 1 no
3 2 1
1 1 yes
2 2 yes
3 3 yes
2 2 1
1 2 yes
2 3 no
5 4 3
1 2 yes
1 3 no
4 5 yes
5 6 yes
6 7 no
0 0 0
【输出样例】
no
no
1
2
end
3
4
5
6
end
讲解
我们将这个列一列,我们发现问自己是不是天神永远说的是yes,而且这道题目不存在违反解的情况,那么我们就可以先处理出每个人之间的关系。
如果一个人说另外一个人是天神,那么说明这两个人是同种身份,如果说不是天神,那么他们是不同身份,这个列一列就知道了。
那么我们很快发现不就是带权并查集吗,不同的不同就是相同。
那么我们就可以处理出对于每个集合与祖先不同的有多少个数字,就可以推出每个集合内两种不同的身份分别有多少人,对于第(i)个集合中两种不同的身份人数为(a_i,b_i),但是我们并不知道(a_i)是哪种身份的,可以(a_i)个人天神,(b_i)个人恶魔,反之也可以。
但是我们又发现他给定了天神数量,那么问题就变成了现在有(k)组数字,每组有两个数字,让你在每组选择一个数字,使得这个数字等于(p1),问方案数。
方案数为(1)输出方案。
一种很简单的思路是设(f[i][j])表示到第(i)组已经有(j)个天神了,类似背包一样瞎跑跑。但是我真的没想出来
于是我用了另外一种方法就是对于每组我们都假设取了(max(a_i,b_i)),那么就有(now)个天神了,很明显我们需要减去(max(a_i,b_i)-min(a_i,b_i)),那么我们就可以用每组的(max(a_i,b_i)-min(a_i,b_i))去填(now-q1),变成经典的01背包,然后计算方案数,很明显,当(a_i=b_i)时,肯定方案数大于(1)。
这里我们把多种方案设为(2),否则方案不断积累会爆long long。
时间复杂度(O(n^2))。
当然这种做法在随机数据下会特别的快。
//3ms猛如虎
#include<cstdio>
#include<cstring>
#define N 1100
using namespace std;
int fa[N],val[N]/*到根的权值*/,n,m,q,p;
int cnt[N],siz[N],bbk[N];//表示当自己为父亲的时候,子树内总共有多少数字和自己不一样。
int findfa(int x)
{
if(fa[x]==x)return x;
int y=findfa(fa[x]);val[x]=val[x]*val[fa[x]];fa[x]=y;//表示的就是转移
return fa[x];
}
bool dp[N],bk[N];int tot[N],pre[N]/*状态*/,use[N];
int main()
{
while(scanf("%d%d%d",&m,&q,&p)!=EOF)
{
if(m==0 && q==0 && p==0)break;
//初始化
n=q+p;
for(int i=1;i<=n;i++)fa[i]=i,val[i]=siz[i]=1,cnt[i]=0,bk[i]=0,bbk[i]=1;//永远都是跟自己相等的
memset(dp,0,sizeof(dp));dp[0]=0;
memset(tot,0,sizeof(tot));
//
for(int i=1;i<=m;i++)
{
int x,y,z;char st[20];scanf("%d%d%s",&x,&y,st+1);
z=(st[1]=='y'?1:-1);
int tx=findfa(x),ty=findfa(y);
if(tx!=ty)fa[tx]=ty,val[tx]=z*val[x]*val[y],siz[ty]+=siz[tx];/*表示他和fa的关系*/
}
for(int i=1;i<=n;i++)
{
int x=findfa(i);//因为我们要算出每个联通块里面有多少个为-1的,同时计算出每个点的祖先。
cnt[x]+=(val[i]==-1);
}
int now=0;
for(int i=1;i<=n;i++)
{
if(fa[i]==i)
{
if(siz[i]-cnt[i]>cnt[i])cnt[i]=siz[i]-cnt[i],bbk[i]=-1/*表示现在的cnt是与fa相等的集合*/;
now+=cnt[i];
}
}
int k=now-q;dp[0]=1;tot[0]=1;
for(int i=1;i<=n;i++)
{
if(fa[i]==i)
{
int x=(cnt[i]<<1)-siz[i];
for(int j=k;j>=x;j--)
{
if(dp[j-x]==1)
{
if(dp[j]==0)dp[j]=1,tot[j]=tot[j-x],pre[j]=j-x,use[j]=i;
else tot[j]=2;//多种方案
}
}
}
}
if(tot[k]>1)printf("no
");
else
{
int x=k;
while(x)
{
bk[use[x]]=1;//标记是哪个块选的是小的集合
x=pre[x];
}
for(int i=1;i<=n;i++)
{
if((bk[fa[i]]==1 && val[i]*bbk[fa[i]]==1) || (bk[fa[i]]==0 && val[i]*bbk[fa[i]]==-1))printf("%d
",i);
}
printf("end
");
}
}
return 0;
}