定义:
(dfn[x]):(x)第一次被访问的时间顺序(时间戳)
搜索树:每个节点只访问一次,所有访问过的边((x,y))构成一棵搜索树
(low[x]):定义为以下节点的时间戳的最小值:
(1.)(subtree(x))中的节点。
(2.)通过(1)条不在搜索树上的边,能够到达(subtree(x))的节点。
代码:
void Tarjan(int x)
{
low[x] = dfn[x] = ++ ind;//初始值
que[ ++ top] = x;//进队
vis[x] = 1;//标记
for (int i = hd[x]; i; i = nxt[i])
{
int y = to[i];//枚举x子节点y
if (!dfn[y])
{
Tarjan(y);
low[x] = min(low[x], low[y]);//在子树中,是x的儿子,直接更新
}
else if (vis[y]) low[x] = min(low[x], dfn[y]);
//已经访问过,且y不是x的儿子,因为dfn[y]一定小于dfn[x],那么用dfn[y]更新(不用low[y]更新,因为它们不一定在同一个强连通分量中,防止更新过头)
}
if (dfn[x] == low[x])//形成了一个环,说明x是一个强连通分量的根
{
cnt ++ ;//新的强连通分量
int now = -1;
do
{
now = que[top -- ];
vis[now] = 0;
col[now] = cnt;//染色,标记now属于当前强连通分量cnt
} while (now != x);//弹出
}
return;
}
应用
缩点
在跑了一遍Tarjan
后,对于原来的每条边对应的点对((x,y)),若(col[x]==col[y]),说明它们在同一个强连通分量内,则它们不用在新的图中连边,否则要连。
伪代码:
for (int x = 1; x <= n; ++ x)
{
for (int i = hd[x]; i; i = nxt[i])
{
int y = to[i];
if (col[x] != col[y]) add(col[x], col[y]);
}
}
割点/割边
割点定义:若去掉无向联通图的某个点后,此图不连通,则该点为割点。割边同理。
判断方法:
割边:(dfn[x]<low[y])(说明从(subtree(y))出发,在不经过(x,y)的前提下,无论走那条边,都无法到达(x)或比(x)更早的节点,这就是一条割边)
割点:(dfn[x]le{low[y]})(和割边同理。特别地,若(x)是搜索树根节点,那么(x)是割点当且仅当它存在至少两个子节点(y_1,y_2)满足条件)
伪代码(割点)
if (low[y] >= dfn[x])
{
t ++ ;
if (x != rt || t > 1) cut[x] = 1;
}