bzoj1051: [HAOI2006]受欢迎的牛(强联通板子)

题目传送门


题目大意:

 1、单向边的图里面,求最受欢迎的奶牛。

 2、最受欢迎的奶牛是一个群体(一个环),要满足“最”这个条件就必须满足(其他点都能指向这个环,而这个环不能再指向其它点)。

思路分析:

1、图中的环,可以通过强连通找出来。

2、本题只要找到那个唯一的,没有出度的ans环,环里的点的个数,就是答案。

(因为这个ans环出度为0,就不追捧别人。他是唯一的“出度为0”,说明其他点都有出度,就都会指向他了。)

====================以下是强连通的代码思路,如果看不懂,直接看代码注解====================

既然本题是板子,那就介绍一下强连通的基础吧:

 1、强连通=最大能相互到达的点的群(也可以理解为“环”或者"环的集合");

2、还有一些相关的概念:联通块、入度、出度等等;

3、强连通的代码实现依靠深搜完成:

3/1、定义每个点有2个参数,一个是时间戳,我用字母 i(很多题解用dfn),一个是自己团的老大,我用字母d(很多题解用low);

 3/2、搜索之前,定义全部点的时间戳和老大都为 0,设置一个栈,用来做当前团队的缓存(找到环就会集体出栈);

3/3、for一次全部点,对于每个时间戳为0的点 i,表示从未被访问过,跑dfs(i);

3/4、时间戳是该点(x)被搜到的次序,老大一开始定义为自己的时间戳。

深搜 x->y,y有三种情况:

  1、y的时间戳为0(说明未被访问过),直接搜他,回溯(找到环了,也就是访问在栈内的点再回头)时用y的老大更新x的老大;

2、y的时间戳不为0(以前被访问过),y在栈内,说明 y -> x是通的,现在 x再次搜到了y,说明 x->y也通,两者能到达,

并且y是栈内(说明y先找到x,x现在绕回到y),用y的时间戳更新x的老大(y是这个环的老大,当然就是x的老大);

3、y时间戳不为0,但又不在栈内。说明 y 是不通 x 的,所以 x通 y ,也不可能成环,所以什么事情也没有发生。

 3/5、回溯时,如果 x 点的老大 还是 自己的时间戳(说明自己就是环里的老大)此时将环内的人出栈,完成。

====================以上是强连通的代码思路,如果看不懂,直接看代码注解====================

上代码:

#include<cstdio>
const int mx=20005;
int n,m,len=0,lx=0,lb=0;
int chu[mx];//用来数每个团队的出度(桶) 
int bb[mx];//用来统计每个团队的人数(桶) 
int l[mx],tou=0;//栈用
struct nod{int h,i,d,v,b;}a[mx];
//点:i是时间戳,d是老大,b是所在团队编号,v是是否在栈内,h是邻接表的last; 
struct nod1{int x,y,gg;}b[mx*5]; //边:gg是next数组

void ins(int x,int y)
{
	len++;b[len].x=x;b[len].y=y;b[len].gg=a[x].h;a[x].h=len;
}

void dfs(int x)
{	//时间戳 i不会再改;d会随时更新 
	a[x].i=a[x].d=++lx;//当前访问的第 lx个点  
	l[++tou]=x; a[x].v=1;//进栈
	for(int i=a[x].h;i>0;i=b[i].gg)
	{
		int y=b[i].y;//对于y,分三种情况讨论 
		if(a[y].i==0)//情况1:未被访问的 
		{
			dfs(y);//直接访问 y
			if(a[y].d<a[x].d) a[x].d=a[y].d;//能回溯:说明已成环,y能问到更小的点,所以更新x点的信息; 
		}
		else if(a[y].v==1)//情况2:y被访问过,还在栈内,说明 y->x是通的,现在 x又能找到y,所以成环了,
		
		{
			if(a[y].i<a[x].d) a[x].d=a[y].i;//y可能作为x这个环的老大,用y.i更新 x.d 
		} 
		//情况3:y被访问过,但不在栈内,那么 x->y,但是 y不通x ,不可能成环,所以什么事都没发生。 
	} 
	//回溯时,如果发现自己是团队老大,就要将栈内的整队人拉走,建立新团队
	if(a[x].i==a[x].d)
	{
		lb++;bb[lb]=0;//诞生新团队编号
		int k;
		while(1)//拉人出栈 
		{
			k=l[tou--]; a[k].v=0; 
			bb[lb]++;//统计本团的人数 
			a[k].b=lb;//更新参数 b 
			if(k==x) break;
		} 
	} 
}

int main()
{
	scanf("%d %d",&n,&m);int x,y;
	for(int i=1;i<=n;i++) //初始化点的信息 
		a[i].i=a[i].d=a[i].h=a[i].v=0;
	for(int i=1;i<=m;i++)//构图 
	{
		scanf("%d %d",&x,&y); ins(x,y);
	}
	for(int i=1;i<=n;i++)//强连通的核心代码 
	{
		if(a[i].i==0) dfs(i);//时间戳为0,表示该点未被访问,开始搜 
	}
	//强连通结束后,每个点的 b参数都得到了满足 
	
	//通过扫描所有的边,找出每个团队的出度情况
	for(int i=1;i<=lb;i++) chu[i]=0;//初始化数出度的桶 
	for(int i=1;i<=len;i++)
	{
		x=b[i].x; y=b[i].y;
		if(a[x].b!=a[y].b) //连接不同团队之间的边,才是度
		{
			chu[a[x].b]++;//x->y,所以是 x所在团队的出度(也叫y的入度) 
		} 
		
	} 
	
	//如果存在(唯一的出度为0的团队),就是答案,否则无解。
	x=0;
	for(int i=1;i<=lb;i++)
	{
		if(chu[i]==0) { x++; y=i;if(x>1) break;}
	} 
	
	if(x==1) { printf("%d\n",bb[y]);}//有唯一的团队(出度为0的)

	return 0;
} 





  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值