题目链接 有一群人,每个人可能是村民也可能是狼。村民必然说真话,狼有可能说假话。每一个人都指认了另外一个人是村民/狼,问一定是村民、一定是狼的人有多少个。
考虑狼是随机说真话或者假话,那么令每个人都为狼,一定存在一个解。因此一定是村民的数目为0。
考虑只连村民边,这样会产生若干个联通分量。可以得出这些联通分量要么是基环树,要么是树。加入狼边以后,这些联通分量必然也只可能是基环树或树。加入狼边的时候,如果这条狼边连接的是两个不同的联通分量,那么完全可以让被指向的联通分量都为狼,这个人就可能是个村民,所以被指向的人不一定为狼,这时狼边在新的连通分量中显然是树边,也就是非环的边。
如果这条狼边的两个端点在同一个连通分量之内,用反证法可以证明,被指向的人必然是狼。同时指认这个人为村民的人自然也必然是狼。此时狼边在新的联通分量中显然是基环树的环中的某一条边。
于是我们就可以在初始的时候只连反向村民边,用并查集维护联通分量,然后依次对狼边判断所指向的人是否为狼,并搜索出其余必为狼的人。
/*ргргрг*/
#include <cstdio>
#include <cstring>
#include <cmath>
#include <queue>
#include <vector>
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long ll;
const int maxn = 200050;
const int INF = 0x3f3f3f3f;
const double eps = 1e-8;
int kase, n, x, no, ans;
int head[maxn], pre[maxn];
char s[15];
struct node
{
int to, nxt;
}e[maxn];
struct point
{
int u, v;
};
void add(int a, int b)
{
e[no].to = b;
e[no].nxt = head[a];
head[a] = no++;
}
int Find(int x)
{
if(x == pre[x]) return x;
return pre[x] = Find(pre[x]);
}
void dfs(int u)
{
ans++;
for(int i = head[u];i != -1;i = e[i].nxt)
{
int v = e[i].to;
dfs(v);
}
}
int main()
{
scanf("%d", &kase);
while(kase--)
{
scanf("%d", &n);
no = 0;
memset(head, -1, sizeof(head));
for(int i = 1;i <= n;i++) pre[i] = i;
queue<point>q;
for(int i = 1;i <= n;i++)
{
scanf("%d%s", &x, s);
if(s[0] == 'w')
{
point st;
st.u = i, st.v = x;
q.push(st);
}
else
{
add(x, i);
int fa = Find(x), fb = Find(i);
if(fa != fb) pre[fa] = fb;
}
}
ans = 0;
while(!q.empty())
{
point now = q.front();
q.pop();
int fu = Find(now.u), fv = Find(now.v);
if(fu != fv) continue;
dfs(now.v);
}
printf("0 %d\n", ans);
}
return 0;
}