poj2186Popular Cows_ 强连通分支_缩点tarjan算法

33 篇文章 0 订阅
26 篇文章 0 订阅

题目链接

   题意:
   每一头牛的愿望就是变成一头最受欢迎的牛。现在有N头牛,给你M对整数(A,B),表示牛A认为牛B受欢迎。 这
种关系是具有传递性的,如果A认为B受欢迎,B认为C受欢迎,那么牛A也认为牛C受欢迎。你的任务是求出有多少头
牛被所有的牛认为是受欢迎的。
 
      先用tarjan求出每个强连通分量,再缩点,统计每个点的出度,如果有且只有1个出度为0的点,就输出这个点包含的节点数,否则输出0.
 
证明:
      由于缩点后是一个DAG图,出度为0的点的个数一定大于等于1.
      如果没有点被孤立,当出度为0的点多于1个时,由DAG图的性质可得,一定不存在一个点能从其他所有点到达。
      只有当出度为0的点的个数等于1时,这个出度为0的点才能被其他所有点到达。
解释:
   Belong[i]记录第i个节点属于的是第几个连通分量。
   in_S[i]记录i是否在栈中
   Bcnt记录连通分支个数。
   size_S[i]记录第i个强连通分支的中节点的个数.

(这个算法其实很简单,可能刚开始弄不懂,多想想多看看就会有新的看法想法,别气馁,加油~)
首先要学习下tarjan算法( 强连通讲解tarjan算法讲解),强连通的实现:
  其实是dfs深搜实现的,需要利用dfn时间戳(记录是第几次遍历)和每次更新low值(记录能回到最早的祖先的dfn值)。
如果是强连通,就一定存在环,但是是有向图,环就两种存在方式(各点相通,不通)。
如果有环,那么这个节点是遍历过的(dfn[v]!=0)。
如何去判断这个环属于哪种形式?
利用栈,每次将在同一个连通分量的节点压入到栈里面,并标记,如果有环,并且还在栈里面,就说明这是一个连通分量。那么就更新low[u]=min(low[u],dfn[v]);
当(dfn[u]==low[u])时候,就是一个连通分量。

可能这道题的测试数据比较水。。。除了蓝桥杯比赛,第一次交这种每个程序测试数据为一次的。。。。
#include<stdio.h>
#include<string.h>
#include<vector>
#include<stack>
using namespace std;

#define min(a,b) a<b?a:b
vector<int>e[50005];
stack<int>S;
int dfn[10005];
int low[10005];
int out[10005];
int Belong[10005],Bcnt;
int time;
bool in_S[10005];
int size_S[10005];

void tarjan(int u)
{
	S.push(u);
	in_S[u]=true;
	dfn[u]=low[u]=++time;
	int i,j,v;
		
		for(i=0;i<e[u].size();i++)
		{
			
			v=e[u][i];
			if(!dfn[v])
			{
				tarjan(v);
			    low[u]=min(low[u],low[v]);
			}
			else if(in_S[v])
			{
				low[u]=min(low[u],dfn[v]);
			}
			
		}
		
		if(dfn[u]==low[u])
		{
			Bcnt++;
			
			int v;
			do{
				v=S.top();
				S.pop();
				Belong[v]=Bcnt;
				in_S[v]=false;
				size_S[Bcnt]++;				
			}while(v!=u);
		
		}
	
	
}
int main()
{
	int n,m,i,j,u,v,num;
	scanf("%d%d",&n,&m);

	for(i=0;i<m;i++)
	{
		scanf("%d%d",&u,&v);
		e[u].push_back(v);
	}
	num=0;time=0,Bcnt=0;
	memset(dfn,0,sizeof(dfn));
	memset(in_S,false,sizeof(in_S));
	memset(out,0,sizeof(out));
	
	for(i=1;i<=n;i++)
	{
		if(!dfn[i])
		    tarjan(i);
	}
//求缩点后的出度	
	for(i=1;i<=n;i++)
	{
		for(j=0;j<e[i].size();j++)
		{
			int v=e[i][j];
			if(Belong[i]!=Belong[v])
			   out[Belong[i]]++;
		}
	
	}
	
	int flag;
	for(i=1;i<=Bcnt;i++)
	{	
	
	   if(out[i]==0)
	   {
	   	num++;
	   	flag=i;
	   }	    
	}
	if(num==1)
	{
		printf("%d",size_S[flag]);
	}
	else
	{
		printf("0");
	}

	return 0;
}





评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值