强连通分量——tarjan算法在有向图中的应用(1)

tarjan算法在各种各样的图论问题中有着广泛的应用。现在,我们讨论tarjan算法在求有向图的强连通分量时的应用。

同求无向图的割点一样,我们需要用到dfs和low两个数组,其意义在此不再赘述。此外我们需要多开一个数组作为辅助栈。

求强连通分量的思路与求割点也很相近。我们对图进行dfs,dfs数组与low数组的求法与求割点时相同,每dfs到一个点,就将其标记为已达,并将其入栈。遍历完点x的所有出边后,若dfs[x]=low[x],则说明x及栈中所有仍然在x以上的点构成一个强连通分量,并将它们出栈。若有需要,可以进行染色等操作。证明如下:若在x的子树中,含有一个不包含x的强连通分量,则在以x为根的dfs结束之前,这个强连通分量一定会被弹出。

这样,我们就求出了一张有向图的强连通分量。

原题:P2341 受欢迎的牛

在这道题中,若一群牛组成一个强连通分量,则若其中的一头奶牛是明星奶牛,则这头奶牛所在的强连通分量中的所有奶牛都是明星奶牛。同时,若某强连通分量有出度,则该强连通分量中的所有奶牛都不是明星奶牛。以及,若有两个及以上的强连通分量的出度为零,则不存在明星奶牛。

关于强连通分量的出度与入度,我们可以在将某强连通分量弹出栈时将其染色,求出所有强连通分量后,我们可以遍历所有的边,若边的两侧颜色不一致,则给起点所在的强连通分量出度++,最后遍历已数出的出入度并判断即可。

AC代码:

#include
#include
using namespace std;
int n,m,head[10005],ecnt=1,x,y,cnt=1,ans;
int dfn[10005],low[10005],v[10005];
int s[10005],dfsc,h,c[10005],out[10005],size[10005];
//v:vis s:stack dfsc:dfs序 h:栈顶
//c:color out:出度 cnt:连通块数量  size:连通块大小 
struct list{
	int r,n;
}l[50005];
void add(int a,int b)
{
	l[ecnt].r=b;
	l[ecnt].n=head[a];
	head[a]=ecnt++;
}
void pop(int x)
{
	int num=1;
	while(s[h-1]!=x)
	{
		num++;
		v[s[h-1]]=0;
		c[s[h-1]]=cnt;
		h--;
	}
	c[x]=cnt;
	h--;
	size[cnt++]=num;
}
void dfs(int x)
{
	low[x]=dfn[x]=++dfsc;
	s[h++]=x;
	v[x]=1;
	for(int i=head[x];i;i=l[i].n)
	{
		int p=l[i].r;
		if(!dfn[p])
		{
			dfs(p);
			low[x]=min(low[p],low[x]);
		}
		else low[x]=min(low[p],low[x]);
	}
	if(dfn[x]==low[x]) pop(x);
}
int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=m;i++) {scanf("%d%d",&x,&y);add(x,y);}
	for(int i=1;i<=n;i++)
	{
		if(!dfn[i]) dfs(i);
		else if(!dfn[i]) {printf("0");return 0;}
	}
	for(int i=1;i<=n;i++)
	{
		for(int j=head[i];j;j=l[j].n)
		{
			int p=l[j].r;
			if(c[p]!=c[i]) out[c[i]]++;
		}
	}
	for(int i=1;i<cnt;i++)
	{
		if(ans&&out[i]==0) {printf("0");return 0;}
		if(out[i]==0) ans=size[i];
	}
	printf("%d",ans);
	return 0;
}  

你可能感兴趣的:(算法,题解)