天天看点

【bzoj 1051】[HAOI2006]受欢迎的牛(Tarjan缩点)

1051: [HAOI2006]受欢迎的牛

Time Limit: 10 Sec   Memory Limit: 162 MB

Submit: 4091   Solved: 2194

[ Submit][ Status][ Discuss]

Description

  每一头牛的愿望就是变成一头最受欢迎的牛。现在有N头牛,给你M对整数(A,B),表示牛A认为牛B受欢迎。 这 种关系是具有传递性的,如果A认为B受欢迎,B认为C受欢迎,那么牛A也认为牛C受欢迎。你的任务是求出有多少头 牛被所有的牛认为是受欢迎的。

Input

  第一行两个数N,M。 接下来M行,每行两个数A,B,意思是A认为B是受欢迎的(给出的信息有可能重复,即有可 能出现多个A,B)

Output

  一个数,即有多少头牛被所有的牛认为是受欢迎的。

Sample Input

3 3

1 2

2 1

2 3

Sample Output

1

HINT

100%的数据N<=10000,M<=50000

Source

[ Submit][ Status][ Discuss]

【题解】【Tarjan缩点,一个受欢迎的牛必定与每个点都连边,所以,缩点,把在一个强连通分量的点缩为一个,然后扫描每一条边,最后找出度为0的点】

(附:了tarjan算法的基础,这道题就可以搞了,如果存在受所有牛都欢迎的牛,那么这些牛不可能独立存在,他们要么在一个强联通块中,要么只有一头牛。那么我们可以把所有强连通块用tarjan算法处理出来,同时计算每个强连通块中点的个数。然后枚举所以的边,如果一条边的两个结点属于不同的强连通块,那么就把发出这条边的联通块的出度+1,最后寻找是否存在出度为0的强连通块,因为受所有牛的欢迎(在一个强联通块中的牛只要有一个欢迎另一头不再强连通块中的牛,那么强连通块中其他牛就欢迎)所以一定所以的强连通块都有边连向这个强联通块。)

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int a[500010],nxt[500010],p[500010],tot;
int dis[500010],dft[500010],root,cnt;
int x[500010],y[500010];
int d[500010],top;
int f[500010],size[500010],num[500010];
int n,m;
bool vis[500010];

inline int tmp(int a,int b)
{
	return a>b;
}
inline void add(int x,int y)
{
	tot++; a[tot]=y; nxt[tot]=p[x]; p[x]=tot;
}

void tarjan(int u)
{
	dis[u]=dft[u]=++cnt;
	d[++top]=u; 
	vis[u]=1;
	int v=p[u];
	while(v>=0)
	 {
	 	if(!dft[a[v]])
	 	 {
	 	 	tarjan(a[v]);
	 	 	dis[u]=min(dis[u],dis[a[v]]);
		  }
		 else
		  if(vis[a[v]]&&dis[u]>dft[a[v]]) dis[u]=dft[a[v]];
		v=nxt[v];
	 }
	int b;
	if(dis[u]==dft[u])
	 {
	 	++root;
	 	do{
	 		b=d[top--];
	 	    size[root]++;
	 		f[b]=root;
	 		vis[b]=0;
		 }while(u!=b);
	 }
	return; 
}
inline void slove()
{
	cnt=top=root=0;
	memset(dis,0,sizeof(dis));
	memset(dft,0,sizeof(dft));
	for(int i=1;i<=n;++i)
	 if(!dft[i]) tarjan(i);
	return;
}
inline void count()
{
    for(int i=1;i<=m;++i)
     if(f[x[i]]!=f[y[i]])
       num[f[x[i]]]++;
    return;
}

int main()
{
	int i,j;
	memset(p,-1,sizeof(p));
	memset(nxt,-1,sizeof(nxt));
	memset(num,0,sizeof(num));
	scanf("%d%d",&n,&m);
	for(i=1;i<=m;++i)
	  {
	  	scanf("%d%d",&x[i],&y[i]);
	  	add(x[i],y[i]);
 	  }//不知道为什么,如果每条边只存一遍,就会WA(重复的边也要存储) 
	slove();
	count();
	int ans=0;
	for(i=1;i<=root;++i)
	  if(!num[i])
	   {
	   	if(ans)
	     {printf("0\n"); return 0;}
	    ans=i;
	   }
	printf("%d\n",size[ans]);
	return 0;
}