题目链接:hdu 6370 Werewolf
Sample Input
1
2
2 werewolf
1 werewolf
Sample Output
0 0
题意:n个人,每个人会说x 是狼或者村民,x是除自己以外的任何人,村民不说谎,狼可能说谎,问一定是村民的人的人数和一定是狼的人的人数。
思路:如果所有人都是狼,显然成立,所以,一定是村民的人的人数一定是0,然后看图一的例子,如果1是村民,那么1说2是村民,2必是村民,2说3是村民,3必是村民,3说1是狼,1必是狼,所以假设不成立,则1是铁狼。故,当有环存在的时候,若环中有且只有一条边是指认狼的边,那么被指认为狼的人,是铁狼。而且,指认铁狼为村民的人,也是铁狼,如图二铁狼为3
一开始觉得反正就一条出去的边,就直接模拟了,结果T了一下午还一直觉得自己是O(n)的, 然后快结束了队友找到了T的地方,就应该趁早跑强连通缩点的,细节地方处理要注意,具体看代码。
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<iostream>
#define N 100005
#define inf 999999999
using namespace std;
int dfn[N],low[N],head[N],sk[N],scc[N],cnt,sccnum,index,tp;
//scc[]值相同的,梭点后属于同一个点,sccnum表示缩点后有几个点
int ans;
int first[N];
int tot;
struct edge{
int next,to,w;
}ed[N*2],e[N*2];
void add(int u,int v,int w){
ed[cnt].w = w;
ed[cnt].to = v;
ed[cnt].next = head[u];
head[u] = cnt++;
}
void add2(int u,int v,int w){
e[tot].w = w;
e[tot].to = v;
e[tot].next = first[u];
first[u] = tot++;
}
void tarjan(int root)
{
dfn[root]=low[root]=++index;
sk[++tp]=root;
int i;
for(i=head[root];~i;i=ed[i].next){
int v=ed[i].to;
if(!dfn[v]){
tarjan(v);
low[root]=min(low[root],low[v]);
}
else if(!scc[v]){
low[root]=min(low[root],dfn[v]);
}
}
if(low[root]==dfn[root]){
sccnum++;
for(;;){
int x=sk[tp--];
scc[x]=sccnum;
if(x==root)break;
}
}
}
void dfs(int i){//i是铁狼
for(int j=first[i];~j;j=e[j].next){
int v=e[j].to;
if(e[j].w==1){//认为铁狼是村民
ans++;
dfs(v);//认为铁狼是村民的人也是铁狼,继续搜
}
}
}
int main(){
int t;
scanf("%d",&t);
int n;
while(t--){
scanf("%d",&n);
if(n==1){
printf("0 0\n");
continue;
}
memset(dfn,0,sizeof(dfn));
memset(low,0,sizeof(low));
memset(scc,0,sizeof(scc));
memset(head,-1,sizeof(head));
memset(first,-1,sizeof(first));
sccnum=0;
index=0;
tp=0;
cnt=0;
tot=0;
for(int i=1;i<=n;i++){
int x;
char s[50];
scanf("%d%s",&x,s);
if(s[0]=='w'){//0为狼边,1为村民边
add(i,x,0);
add2(x,i,0);//反向建图
}
else{
add(i,x,1);
add2(x,i,1);
}
}
for(int i=1;i<=n;i++){
if(!dfn[i])tarjan(i);
}
int sum[100005];//每个环中狼边数量
int num[100005];//缩点后的点包含的点的个数,大于1才有环
int wolf[100005];//每个环中狼边的头u
memset(wolf,0,sizeof(wolf));
memset(sum,0,sizeof(sum));
memset(num,0,sizeof(num));
for(int i=1;i<=n;i++){
num[scc[i]]++;
}
for(int i=1;i<=n;i++){
if((ed[head[i]].w==0)&&(num[scc[i]]>1)){//注意head[i]才是u,不是ed[i]
sum[scc[i]]++;
wolf[scc[i]]=head[i];
}
}
ans=0;
for(int i=1;i<=sccnum;i++){
if(sum[i]==1){//狼边数为1的环才有铁狼
int u=wolf[i];
int v=ed[u].to;
ans++;
dfs(v);//寻找指认铁狼为村民的人,因为之前反向建图了,直接搜索即可
}
}
printf("0 %d\n",ans);
}
return 0;
}
/*
1
8
2 v
4 v
2 w
3 v
6 v
8 w
6 v
7 v
0 3
*/