POJ2186 Popular Cows

本文详细介绍了求解强连通分量的Tarjan算法,并阐述了点压缩的概念及其在特定场景下的应用。通过分析算法原理和提供AC_CODE示例,深入探讨如何将强连通分量作为单点处理,从而简化问题解决过程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

强连通分量+点压缩

强连通分量是指非强连通图中的极大强连通子图,求强连通分量的算法常用的有Tarjan和Kosaraju两种,它们的复杂度均为O(n+m),n图中点的个数,m为边的个数。只看了下Tarjan,没有仔细了解Kosaraju。这里有对Tarjan比较详细的说明,说的都很好:

http://www.byvoid.com/blog/scc-tarjan/

http://www.cnblogs.com/saltless/archive/2010/11/08/1871430.html

这道题总的过程就是首先利用Tarjan求出所有的强连通分量,然后将每个强连通分量当作一个点(即点压缩,因为在强连通分量内部所有的点都是两两可到达的)。然后求每个强连通分量的出度,题目最终的答案则为出度为0的强连通分量内点的个数。具体证明可见:

http://www.cppblog.com/RyanWang/archive/2009/02/26/74984.aspx

算法证明:
1:假设a和b都是最受欢迎的cow,那么,a欢迎b,而且b欢迎a,于是,a和b是属于同一个连通分量内的点,所有,问题的解集构成一个强连通分量。
2:如果某个强连通分量内的点a到强连通分量外的点b有通路,因为b和a不是同一个强连通分量内的点,所以b到a一定没有通路,那么a不被b欢迎,于是a所在的连通分量一定不是解集的那个连通分量。
3:如果存在两个独立的强连通分量a和b,那么a内的点和b内的点一定不能互相到达,那么,无论是a还是b都不是解集的那个连通分量,问题保证无解。
4:如果图非连通,那么,至少存在两个独立的连通分量,问题一定无解。

AC_CODE:

#include <stdio.h>
#include <memory.h>

#define N 10001
#define M 50001

struct edge
{
	int v;
	edge *next;
};
edge *vertices[N];
edge edges[M];
int cnt;
int idx;
int dfn[N];
int low[N];
int instack[N];
int stack[M];
int top=-1;
int scc[N];
int nscc;//强连通分量的编号
int outd[N];

void tarjan(int u)
{
	int v;
	edge *head=vertices[u];
	dfn[u]=low[u]=++idx;
	stack[++top]=u;
	instack[u]=1;
	for(;head;head=head->next)
	{
		v=head->v;
		if(!dfn[v])
		{
			tarjan(v);
			low[u]=(low[u]<low[v])?low[u]:low[v];
		}
		else if(instack[v])
			low[u]=(low[u]<dfn[v])?low[u]:dfn[v];
	}
	if(low[u]==dfn[u])
	{
		++nscc;
		do 
		{
			v=stack[top--];
			scc[v]=nscc;
			instack[v]=0;
		}while(u!=v);
	}
	return;
}
void calc_outd(int n)
{
	int ans=0,cnt=0,v;
	edge *head;
	for(int i=1;i<=n;i++)
	{
		head=vertices[i];
		for(;head;head=head->next)
		{
			if(scc[head->v]!=scc[i])
				outd[scc[i]]++;
		}
	}
	for(int i=1;i<=nscc;i++)
	{
		if(!outd[i])
		{
			v=i;
			cnt++;
		}
	}
	//只有出度为0的强连通分量个数为1时,才有解
	if(cnt==1)
	{
		for(int j=1;j<=n;j++)
			if(scc[j]==v) ans++;
	}
	printf("%d\n",ans);
}
int main()
{
	int n,m,a,b;
	scanf("%d%d",&n,&m);
	memset(vertices,0,sizeof(vertices));
	memset(dfn,0,sizeof(dfn));
	memset(low,0,sizeof(low));
	memset(instack,0,sizeof(instack));
	memset(outd,0,sizeof(outd));
	while(m--)
	{
		scanf("%d%d",&a,&b);
		edges[cnt].v=b;
		edges[cnt].next=vertices[a];
		vertices[a]=&edges[cnt++];
	}
	for(int i=1;i<=n;i++)
		if(!dfn[i]) tarjan(i);
	calc_outd(n);
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值