Tarjan专题总结复习
定义:
\(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]);
}
}
割点/割边
注意:无向图中不要记\(vis\),只用传一条边进去即可,但是此时一个点也会被判为强连通分量
割点定义:若去掉无向联通图的某个点后,此图不连通,则该点为割点。割边同理。
判断方法:
割边:\(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;
}