Tarjan在图论中的应用(一)——用Tarjan来实现强连通分量缩点

前言

T a r j a n Tarjan Tarjan是一个著名的将强连通分量缩点的算法。


大致思路

它的大致思路就是在图上每个联通块中任意选一个点开始进行Tarjan操作(**依据:**强连通分量中的点可以两两到达,因此从任意一个点开始都没关系)。


具体实现

对于每一个点,先记录它的dfs序,并将该点加入一个栈中,并标记其在栈中,然后用 l o w [ ] low[] low[]数组来记录从它出发能到达的字典序最小的节点。

枚举它所能到达的每一个节点,并对每一个节点进行分类讨论:

设当前节点为 x x x,枚举到的节点为 s o n son son

如果 s o n son son没有被访问过,就先对它进行Tarjan操作,然后更新 l o w [ x ] low[x] low[x] l o w [ x ] = m i n ( l o w [ x ] , l o w [ s o n ] ) low[x]=min(low[x],low[son]) low[x]=min(low[x],low[son]))。

如果 s o n son son已经被访问过,又分两种情况:

  • 如果 s o n son son在栈中,那么更新 l o w [ x ] low[x] low[x]

  • 如果 s o n son son不在栈中,那么代表已经对 s o n son son所能到达的每一个节点操作过,说明从 s o n son son不能到达 x x x,即它们不在同一个强连通分量中,因此更新 l o w [ x ] low[x] low[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】调查任务

L i n k Link Link

【洛谷2403】[SDOI2010] 所驼门王的宝藏 的题解详见博客【洛谷2403】[SDOI2010] 所驼门王的宝藏(Tarjan+dfs遍历)

【51nod1815】调查任务 的题解详见博客【51nod1815】调查任务(Tarjan+拓扑)

你可能感兴趣的:(强连通分量缩点)