P2341 [HAOI2006]受欢迎的牛--很细--Sabrina--Sabrinadol

题目背景
本题测试数据已修复。

题目描述
每头奶牛都梦想成为牛棚里的明星。被所有奶牛喜欢的奶牛就是一头明星奶牛。所有奶

牛都是自恋狂,每头奶牛总是喜欢自己的。奶牛之间的“喜欢”是可以传递的——如果A喜

欢B,B喜欢C,那么A也喜欢C。牛栏里共有N 头奶牛,给定一些奶牛之间的爱慕关系,请你

算出有多少头奶牛可以当明星。

输入输出格式
输入格式:
 第一行:两个用空格分开的整数:N和M

 第二行到第M + 1行:每行两个用空格分开的整数:A和B,表示A喜欢B

输出格式:
 第一行:单独一个整数,表示明星奶牛的数量

输入输出样例
输入样例#1:
3 3
1 2
2 1
2 3
输出样例#1:
1
说明
只有 3 号奶牛可以做明星

【数据范围】

10%的数据N<=20, M<=50

30%的数据N<=1000,M<=20000

70%的数据N<=5000,M<=50000

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

题解

对于本题各位大佬的愿望肯定是AC,所以我们直接讲满分算法

1.题目分析

本题的意思大概就是我们选出的明星牛每头牛都喜欢,将牛看做一个点,牛与牛之间的喜欢看做一条路
,那么我们是不是就相当于在一个有向图中去找强连通子图了对吧

2.主要算法分析

(1)因为同一头牛被所有牛喜欢,那么它不能喜欢任意除了自己这个联通子图的牛的其他牛,所以在每个联通子图下统计是否出度为0,就好了。同样,如果存在两个或两个以上的出度为0的点(已经缩点之后),那么直接输出0就好了。
(2)那么既然要找强连通子图,就必须要用到Tarjan算法

我摘抄一段Tarjan算法的讲解(懒。。)

Tarjan算法介绍:
Tarjan算法是图论中的一种算法,用作于图的联通性
它可以做什么?

根据 Robert Tarjan 的名字命名的算法Tarjan算法可以在线性时间内求出无向图的割点与桥,再进一步的求出双联通分量,也在数据结构上做出了贡献。

Tarjan算法的用途
1.求桥和割点
2.求点和边的双连通分量
3.求强连通*

做法基础

Tarjan算法基于图的深度优先遍历上(没错!就是与深度优先搜索(DFS)一样的东西)

(如果没学过这样东西的人可以先收藏一下,等学过了在看)
Targan算法的流程
利用dfs来遍历图来构建一种数型的结构

Tarjan算法的两个核心数组

dfn:我们用dfn数组记录

low:我们用low[i]表示一个节点的子树中可以到达最小的dfn

(显然对于一个刚刚遍历到的点我们给他赋上一个新的dfn,low)

摘自:huangdanning-HDN
3.tarjan算法完美结束之后
我们需要进行判断该点及其子孙是否能够够成一个强连通子图,这里只需要判断一下
dfn[u]==low[u]就好了
4.对于2 ,3给出片段代码
代码注释很详细

void tarjan(int u)
{
	num++;
	dfn[u]=num;//时间戳 
	low[u]=num;//初始化这个low 
	st[++top]=u;//入栈 
	for(int i=first[u];i;i=next[i])//邻接表遍历 
	{
		int v=to[i];//存储到达的点 
		if(!dfn[v])//如果该点还没有被扫过,时间戳为0 
		{
			tarjan(v);//那就向下扫方便找low 
			low[u]=min(low[u],low[v]);//low取最小值,看看能不能成环 
		}
		else if(!co[v])//判断是否在栈中 
		{
			low[u]=min(low[u],dfn[v]);
		}
	}
	if(low[u]==dfn[u])//如果相等就记录,出栈
	{
		co[u]=++col;//co存强连通分量元素 ,其值代表在第几个强连通子图中
		++si[col];//代表每个强联通子图中对应的元素个数
		while(st[top]!=u)
		{
			++si[col];
			co[st[top]]=col;
			--top;
		}
		--top;//将st【top】(==u)出栈
	}
}

5.因为题目中给出的数据极大如果用邻接矩阵势必要超出空间,所以我们需要使用

邻接表

来存图,如下,有兴趣的可以试一试前向星
大致内容就是开三个数组一个存点,另外两个存边,代码中有解释

void getsin(int a,int b)
{
	total++;
	to[total]=b;//to来存储点
	next[total]=first[a];//next表示接下来的边
	first[a]=total;//first表示该点连的第一条边
}

cin>>n>>m;
	for(int i=1,x,y;i<=m;i++)
	{
		scanf("%d %d",&x,&y);
		getsin(y,x);//将出度巧妙转化为入度
	}

附上AC代码

#include<cstdio>
#include<algorithm>
#include<iostream>
#include<cstring>
#define maxn 100005
using namespace std;
int n,m;
int to[maxn];
int num;
int st[maxn];
int stack[maxn];
int top;
int col,dfn[maxn],low[maxn],de[maxn],si[maxn];
int co[maxn];
int next[maxn],first[maxn];
int total;
void getsin(int a,int b)
{
	total++;
	to[total]=b;
	next[total]=first[a];
	first[a]=total;
}
void tarjan(int u)
{
	num++;
	dfn[u]=num;
	low[u]=num;
	st[++top]=u;
	for(int i=first[u];i;i=next[i])
	{
		int v=to[i];
		if(!dfn[v])
		{
		tarjan(v);
			low[u]=min(low[u],low[v]);
		}
		else if(!co[v])
		{
			low[u]=min(low[u],dfn[v]);
		}
	}
	if(low[u]==dfn[u])
	{
		co[u]=++col;
		++si[col];
		while(st[top]!=u)
		{
			++si[col];
			co[st[top]]=col;
			--top;
		}
		--top;
	}
}
int main()
{
	cin>>n>>m;
	for(int i=1,x,y;i<=m;i++)
	{
		scanf("%d %d",&x,&y);
		getsin(y,x);
	}
	for(int i=1;i<=n;i++)
	{
		if(!dfn[i])	
		tarjan(i);
	}
	for(int i=1;i<=n;i++)
	{
		for(int j=first[i];j;j=next[j])
		{
			if(co[i]!=co[to[j]])//在不同的强连通图中
			de[co[to[j]]]++;//求入度
		}
	}
	int ans=0;
	int u=0;
	for(int i=1;i<=col;i++)
	{
		if(!de[i])//如果入度为零说明为明星牛
		{
			ans=si[i];//si【i】代表的图中一共有多少头牛
			u++;
		}
	}
	if(u==1)
	cout<<ans;
	else
	cout<<"0";//如果超过2个或等于0都是没有明星牛的
	while(1)
	cout<<"Sabrina"<<endl//防抄
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值