首先介绍以下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] ); } }