首先介绍以下tarjan算法:
---------------------------------------------tarjan算法-----------------------------------------------------------
tarjan算法思想:
tarjan算法在离线求LCA,强连通分量,割边,割点,点双连通分量,边双连通分量很有用
tarjan算法中主要维护的是两个数组,dfn[i]数组存的是深搜各个点的时间戳,low[i]记录的是i能够直接通过其深搜子树里的点间接达到的时间戳最小的点。
然后深搜过程中不断给点加时间戳,对于当前点u,点v与u相连,如果点v不是点u的父亲,如果点v尚未标记,那么先对点v及其子树加时间戳,然后回溯的时候利用除了点u父亲之外的点更新low[u],如果当前儿子已经标记了时间戳,那么证明它之前已经被访问过,那么利用dfn[v]更新low[u]
tarjan算法就是在对所有加时间戳的过程中维护这两个数组。
那么我们结合求割点和割边来具体讲解tarjan算法的实际应用:
----------------------------------------tarjan算法求割点--------------------------------------------------------
割点:在一个无向连通图中,如果去掉这个点以及连向它的边,那么这个图不再连通,那么这个点就是割点
那么我们根据割点的性质,在深搜树中,如果对于某个点u,与它相连的点v(v不是u的父亲),那么如果low[v]>=dfn[u],那么也就是以v为根的深搜子树中的点所连接的点没有已经标记时间戳的,也就是以v为根的子树是封闭的,那么一旦去掉点u,这棵子树中的点就称为了一个新的连通分量,那么点u就是割点了
void dfs ( int u , int pre )
{
int i,j,v;
dfn[u] = low[u] = ++ts;
stk[++s] = u;
for ( i = head[u] ; i != -1 ; i = e[i].next )
{
v = e[i].v;
if ( !dfn[v] )
{
dfs ( v ,u );
low[u] = min ( low[u],low[v]);
//求割点,利用割点划分出点双连通分量
if ( dfn[u] <= low[v] )
{
memset ( in , 0 , sizeof (in));
in[u] = 1;
while ( stk[s] != v )
{
in[stk[s]]=1;
s--;
}
in[v] = 1;
s--;
memset (color,-1,sizeof(color) );
if ( paint(u , 1 , -1 ) )
{
for (j =1 ; j <= n ; j++ )
if ( in[j] == 1 ) flag[j]=true;
}
}
}
else if ( v != pre ) low[u] = min ( low[u] , dfn[v] );
}
}
-------------------------------------------割边--------------------------------------------------------------------------
割边:如果去掉某一条边之后,联通分量的数目变多,那么这个点就是割边
还是利用low[u]数组和dfn[u]数组来判断割边,对于一条边如果它是割边的话,那么low[v] > dfn[u] ,也就是以v为根的子树是封闭的,只要去掉u,v连接的这条边,就会增加联通分量的数量
void tarjan ( int u , int p )
{
dfn[u] = low[u] = ++times;
for ( int i = head[u] ; ~i ; i = e[i].next )
{
int v = e[i].v;
if ( !dfn[v] )
{
tarjan(v,u);
low[u] = min ( low[u] , low[v] );
if ( low[v] > dfn[u] && !e[i].tag )
ans.push_back ( e[i].id );
}
else if ( v != p )
low[u] = min ( low[u] , dfn[v] );
}
}