tarjan算法在各种各样的图论问题中有着广泛的应用。现在,我们讨论tarjan算法在求有向图的强连通分量时的应用。
同求无向图的割点一样,我们需要用到dfs和low两个数组,其意义在此不再赘述。此外我们需要多开一个数组作为辅助栈。
求强连通分量的思路与求割点也很相近。我们对图进行dfs,dfs数组与low数组的求法与求割点时相同,每dfs到一个点,就将其标记为已达,并将其入栈。遍历完点x的所有出边后,若dfs[x]=low[x],则说明x及栈中所有仍然在x以上的点构成一个强连通分量,并将它们出栈。若有需要,可以进行染色等操作。证明如下:若在x的子树中,含有一个不包含x的强连通分量,则在以x为根的dfs结束之前,这个强连通分量一定会被弹出。
这样,我们就求出了一张有向图的强连通分量。
原题:P2341 受欢迎的牛
在这道题中,若一群牛组成一个强连通分量,则若其中的一头奶牛是明星奶牛,则这头奶牛所在的强连通分量中的所有奶牛都是明星奶牛。同时,若某强连通分量有出度,则该强连通分量中的所有奶牛都不是明星奶牛。以及,若有两个及以上的强连通分量的出度为零,则不存在明星奶牛。
关于强连通分量的出度与入度,我们可以在将某强连通分量弹出栈时将其染色,求出所有强连通分量后,我们可以遍历所有的边,若边的两侧颜色不一致,则给起点所在的强连通分量出度++,最后遍历已数出的出入度并判断即可。
AC代码:
#include<cstdio>
#include<algorithm>
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;
}