tarjan算法
时间戳dfn[x]:节点x第一次被访问的顺序
追溯值low[x]:从节点x出发,能访问到的最早时间戳
三步 入,回,离
1入:入x时,盖戳dfn[x]=lowx[x]=tot,tot:被访问的顺序
2枚举:枚举x的邻接的节点y
- 邻接点未访问时,对y深搜,回x节点时,用low[y]更新low[x]
子节点y能访问到的节点,x一定的能访问到,x时y的父节点
- 若y已访问且在栈中,说明y是祖先节点或左子树节点
- y已经不在栈中,不做处理
3离开x时,判断是否是强连通分量的根节点,即dfn[x]==low[x]
记录强连通分量,遍历完一个强连通分量才可出栈
洛谷:P2863 [USACO06JAN]The Cow Prom S
https://www.luogu.com.cn/problem/P2863
import sys
sys.setrecursionlimit(100000000)
n,m=map(int,input().split())
e=[[] for i in range(n+1)]
for i in range(m):
a,b=map(int,input().split())
e[a].append(b)
dfn=[0]*(n+1)
low=[0]*(n+1)
stk=[0]*(n+1)
instk=[0]*(n+1)
scc=[0]*(n+1)
siz=[0]*(n+1)
tot=cnt=top=0
vis=[0]*(n+1)
def tarjan(x):
global tot,cnt,top
tot=tot+1
top=top+1
vis[x]=1
dfn[x]=low[x]=tot#入x,盖戳,入栈
stk[top]=x
instk[x]=1
for l in e[x]:
if not dfn[l]:
tarjan(l)
low[x]=min(low[x],low[l])#回
elif instk[l]:
low[x]=min(low[x],low[l])
if dfn[x]==low[x]:#离,x是scc的根
cnt=cnt+1
while True:
y=stk[top]#弹栈
top=top-1
instk[y]=0#不在栈中,标记为0
scc[y]=cnt#scc的编号,属于第几个联通图
siz[cnt]=siz[cnt]+1#scc的大小
if y==x:
break
for i in range(1,n+1):
if vis[i]==0:
tarjan(i)
ans=0
for i in range(1,n+1):
if siz[i]>=2:
ans=ans+1
print(ans)