2018 Multi-University Training Contest 6 1009 Werewolf(hdu 6370)(缩点)

题目链接: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
*/

猜你喜欢

转载自blog.csdn.net/yz467796454/article/details/81514134
今日推荐