题意
狼人游戏。狼人可能说谎,村民不会说谎。N个人每个人说一句话,“x是狼人”或者“x是村民”。
求一定是村民的人的数量,一定是狼人的人的数量。
题解
因为狼人可以不说谎,即狼人可以伪装成村民,故不存在一定是村民的人。
然后,如果A说B是村民,B说A是狼人,那么A一定是狼人。
同样,如果A说B是村民,B说C是村民,C说A是狼人,那么A一定是狼人。总而言之,一个N条边的环,有N-1条村民边,1条狼人边,那么狼人边所指认的狼人一定就是狼人。
根据确定的狼人,指认狼人为村民的人也一定是狼人。
我们可以将这些指认关系建边,狼人边用边权1表示,村民边用边权0表示。将所有的村民边合并到并查集中。对所有的狼人边进行查询,如果边两端在一个集合中,那么它们形成了一个环。这个环中有N条边,其中N-1条村民边,1条狼人边。
将查询出来的狼人放入队列中,然后沿着指认狼人为村民的边进行BFS计数即可。
AC代码
#include <bits/stdc++.h>
using namespace std;
const int maxn=1e5+7;
struct edge
{
int from,to,next,w;//w=1表示狼
}e[maxn];
int head[maxn],cnt;
void init()
{
memset(head,-1,sizeof(head));
cnt=-1;
}
void add_edge(int u,int v,int w)
{
e[++cnt].to=v;
e[cnt].from=u;
e[cnt].w=w;
e[cnt].next=head[u];
head[u]=cnt;
}
int par[maxn];
int find(int x)
{
return par[x]==x?x:find(par[x]);
}
void unit(int x,int y)
{
int fx=find(x),fy=find(y);
if(fx==fy) return ;
par[fx]=fy;
}
bool vis[maxn];
int main()
{
int T;
scanf("%d",&T);
while(T--)
{
int n;
scanf("%d",&n);
init();
for(int i=0;i<=n;i++) par[i]=i;
for(int i=1;i<=n;i++)
{
int x;
char s[20];
scanf("%d%s",&x,s);
if(s[0]=='w') add_edge(x,i,1);
else add_edge(x,i,0),unit(x,i);//村民边合并以判环
}
memset(vis,false,sizeof(vis));
queue<int> que;
int ans=0;
for(int i=0;i<=cnt;i++)
{
int u=e[i].from,v=e[i].to,w=e[i].w;
if(find(u)==find(v) && w==1 && !vis[u])
{
vis[u]=true;
ans++;
que.push(u);
}
}
while(!que.empty())
{
int u=que.front();que.pop();
for(int i=head[u];i!=-1;i=e[i].next)
{
int v=e[i].to,w=e[i].w;
if(vis[v]) continue;
if(w) continue;
vis[v]=true;
ans++;
que.push(v);
}
}
printf("0 %d\n",ans);
}
return 0;
}