Kosaraju算法的原理:先对逆图作一遍后序遍历,计算访问时间f[u](如果图是一个DAG(有向无环图),这一过程产生一个拓扑排序序列)。然后在原图上再一次DFS,不过是从f[u]最大的未访问点开始遍历。
这样得到的就是一个强连通分量了。因为当原图中w点可以到达v点的时候,在访问逆图的时,逆图中的dfs树中v才可能是w的父节点,这样,f[v]将大于f[w]。再在原图中作DFS,由于是优先访问具有最高后序编号的未访问的结点,如果在原图中v不可到达w,则v和w不可能在同一棵dfs树内,那么v和w不在同一连通分支中。就这么简单的方法。
注意到按照f[u]递减的顺序dfs实际上就是按照拓扑排序的逆序访问各顶点。
#include
<
iostream
>
using
namespace
std;
#define
MAXN 10001
struct
Edge{
int
v,next;
}edg[
10
*
MAXN];
int
p[MAXN],p1[MAXN],n,m,pcnt,post[MAXN],postcnt,c;
int
visited[MAXN];
int
cdeg[MAXN];
void
dfs(
int
u){
int
i,v;
for
(i
=
p1[u];i
!=-
1
;i
=
edg[i].next){
v
=
edg[i].v;
if
(
!
visited[v]){
visited[v]
=
1
;
dfs(v);
}
}
post[postcnt
++
]
=
u;
}
void
dfs1(
int
u){
int
i,v;
for
(i
=
p[u];i
!=-
1
;i
=
edg[i].next){
v
=
edg[i].v;
if
(
!
visited[v]){
visited[v]
=
c;
dfs1(v);
}
}
}
int
main(){
int
i,j,u,v,cnt,ans,mark;
while
(scanf(
"
%d%d
"
,
&
n,
&
m)
!=
EOF){
memset(p,
-
1
,
sizeof
(p));
memset(p1,
-
1
,
sizeof
(p1));
pcnt
=
0
;
for
(i
=
0
;i
<
m;i
++
){
scanf(
"
%d%d
"
,
&
u,
&
v);
edg[pcnt].next
=
p[u];
edg[pcnt].v
=
v;
p[u]
=
pcnt
++
;
edg[pcnt].next
=
p1[v];
edg[pcnt].v
=
u;
p1[v]
=
pcnt
++
;
}
memset(visited,
0
,
sizeof
(visited));
//
反向图,求f[u]
postcnt
=
0
;
for
(i
=
1
;i
<=
n;i
++
){
if
(
!
visited[i]){
visited[i]
=
1
;
dfs(i);
}
}
c
=
0
;
memset(visited,
0
,
sizeof
(visited));
//
正向图
for
(i
=
postcnt
-
1
;i
>=
0
;i
--
){
if
(
!
visited[post[i]]){
visited[post[i]]
=++
c;
//
标记各SCC
dfs1(post[i]);
}
}
memset(cdeg,
0
,
sizeof
(cdeg));
//
计算各SCC的出度
for
(i
=
1
;i
<=
n;i
++
){
for
(j
=
p[i];j
!=-
1
;j
=
edg[j].next){
if
(visited[i]
!=
visited[edg[j].v]){
cdeg[visited[i]]
++
;
}
}
}
cnt
=
0
;
for
(i
=
1
;i
<=
c;i
++
){
//
计算出度为0的SCC个数
if
(cdeg[i]
==
0
){
cnt
++
;
mark
=
i;
}
}
if
(cnt
>
1
){
printf(
"
0\n
"
);
continue
;
}
ans
=
0
;
for
(i
=
1
;i
<=
n;i
++
){
if
(visited[i]
==
mark)
ans
++
;
}
printf(
"
%d\n
"
,ans);
}
return
0
;
}