用Tarjan来实现强连通分量缩点

Tarjan是一个著名的将强连通分量缩点的算法。
它的大致思路就是在图上每个联通块中任意选一个点开始进行Tarjan操作(依据:强连通分量中的点可以两两到达,因此从任意一个点开始都没关系)。
对于每一个点,先记录它的dfs序,并将该点加入一个栈中,并标记其在栈中,然后用 l o w [ ] 数组来记录从它出发能到达的字典序最小的节点。
枚举它所能到达的每一个节点,并对每一个节点进行分类讨论:
设当前节点为 x ,枚举到的节点为 s o n
如果 s o n 没有被访问过,就先对它进行Tarjan操作,然后更新 l o w [ x ] l o w [ x ] = m i n ( l o w [ x ] , l o w [ s o n ] ) )。
如果 s o n 已经被访问过,又分两种情况:
●如果 s o n 在栈中,那么更新 l o w [ x ]
●如果 s o n 不在栈中,那么代表已经对 s o n 所能到达的每一个节点操作过,说明从 s o n 不能到达 x ,即它们不在同一个强连通分量中,因此更新 l o w [ x ]
在枚举完每一个节点后,我们可以判断当前节点是否就是它能到达的dfs序最小的节点,如果是的话,说明它是一个强连通分量中最早被访问过的(当然,也有可能说明它所在的强连通分量中就只有它一个节点),否则,就说明还有比它更早被访问过的,那么退出函数。
如果当前节点是一个强连通分量中最早被访问到的,那么就说明栈中在它上面的节点全都和它在一个强连通分量中,我们可以新建一个强连通分量,并将它连同在它上面的点全部加入这个强连通分量中即可(加入的同时要注意更新这个强连通分量的信息,可参考例题)。
模板如下:

inline void Tarjan(int x)//x是当前访问到的节点
{
    dfn[x]=low[x]=++d,Stack[++top]=x,vis[x]=1;//记录当前节点的dfs序与当前节点所能到达的dfs序最小的点,将当前节点加入栈中,并标记当前节点在栈中
    for(register int i=lnk[x];i;i=e[i].nxt)//枚举从当前节点出发的每一条边
    {
        if(!dfn[e[i].to]) Tarjan(e[i].to),low[x]=min(low[x],low[e[i].to]);//如果这个节点没访问过,就先对这个节点进行操作,然后更新当前节点能到达的dfs序最小的点
        else if(vis[e[i].to]) low[x]=min(low[x],low[e[i].to]);//否则,如果这个点在栈中,就进行更新
    }
    if(low[x]==dfn[x])//如果当前节点就是当前节点能到达的dfs序最小的点,则对当前强连通分量进行缩点
    {
        a[x].col=++cnt,vis[x]=0;//给当前节点加入一个新的强连通分量,并标记当前节点已出栈(如果需要,还要初始化这个强连通分量的信息,可参考例题)
        while(Stack[top]^x) a[Stack[top]].col=cnt,vis[Stack[top--]]=0;//将栈中当前节点之上的节点一一弹出(如果需要,还要同时更新这个强连通分量的信息)
        --top;//将当前节点弹出
    }
}

例题1:【洛谷2403】[SDOI2010]所驼门王的宝藏
例题2:【51nod1815】调查任务

猜你喜欢

转载自blog.csdn.net/chenxiaoran666/article/details/81264791
今日推荐