作用
只要两边dfs,相比于用tarjan求强连通分量,kosaraju算法还可以顺便求出强连通分量的拓扑序,有利于之后的运算.
实现方法
首先以任意一点开始dfs,并记录下搜索的后序遍历,之后将所有边反向,每次从没有搜过的点中选择后序遍历最大的搜索,此次搜到的点均与该点属于同一个强连通分量,直到所有点都被搜到过停止,可以发现越先求出的强连通分量,拓扑序越小.
例题(poj 2186)
题意
给出一副有向图,问有几个点是所有点都可以走到的.
做法
不难发现如果点i符合题意,那么有且仅有点i所在的强连通分量符合题意,还可以发现如果答案非零,则点i所在的强连通分量的拓扑序最大,因此可以用kosaraju算法求出拓扑序最大的,再检验是不是符合题意.
代码
#include<iostream>
#include<cstdio>
#include<cstring>
#define N 10010
#define M 50010
using namespace std;
int n,m,first[N],bb,a[M],b[M],tim[N],tt,lt,ans,last,an;
bool vis[N];
struct Bn
{
int to,next;
} bn[M];
inline void add(int u,int v)
{
bb++;
bn[bb].to=v;
bn[bb].next=first[u];
first[u]=bb;
}
void dfs(int now)
{
vis[now]=1;
int p,q;
for(p=first[now]; p!=-1; p=bn[p].next)
{
if(!vis[bn[p].to])
dfs(bn[p].to);
}
tim[++tt]=now;
}
void Dfs(int now)
{
ans++;
vis[now]=1;
int p,q;
for(p=first[now]; p!=-1; p=bn[p].next)
{
if(vis[bn[p].to]) continue;
Dfs(bn[p].to);
}
}
void df(int now)
{
an++;
vis[now]=1;
int p,q;
for(p=first[now];p!=-1;p=bn[p].next)
{
if(!vis[bn[p].to])
df(bn[p].to);
}
}
int main()
{
memset(first,-1,sizeof(first));
int i,j,p,q;
cin>>n>>m;
for(i=1; i<=m; i++)
{
scanf("%d%d",&p,&q);
add(p,q);
a[i]=p,b[i]=q;
}
for(i=1;i<=n;i++)
{
if(!vis[i])
dfs(i);
}
memset(first,-1,sizeof(first));
bb=0;
for(i=1; i<=m; i++)
{
add(b[i],a[i]);
}
memset(vis,0,sizeof(vis));
for(i=n;i>=1;i--)
{
if(vis[tim[i]]) continue;
ans=0;
Dfs(tim[i]);
last=tim[i];
}
memset(vis,0,sizeof(vis));
df(last);
if(an!=n)
{
puts("0");
return 0;
}
cout<<ans;
}