• 从缩点到圆方树


    一些概念

    连通:无向图中的任意两点都可以互相到达。

    强连通:有向图中的任意两点都可以互相到达。

    连通分量:无向图的极大连通子图。

    强连通分量:有向图的极大强连通子图。


    DFS 生成树:对一张图(有向无向均可)进行深度优先遍历得到的生成树。

    树边:在 DFS 生成树上的边。

    前向边:由子树的根连向子树内的非树边。

    返祖边:由结点连向其祖先的边。

    横叉边:除上面三种之外的边。


    割点:无向连通图删除某个点及其所有连边后,图不再联通,则该点是图的一个割点。

    割点(桥):无向连通图删除某条边后,图不再联通,则该边是图的一条割边。

    点双联通图:不存在割点的无向联通图。

    边双联通图:不存在割边的无向联通图。

    点双联通分量:无向连通图的极大点双联通子图。

    边双联通分量:无向连通图的极大边双联通子图。

    缩点(求强连通分量)

    对于结点 (u),记录两个信息 (dfn_u)(low_u)

    (dfn) 表示时间戳,即第几个被遍历到的点。

    (low) 表示从当前点开始,经过的边的两个端点均处在未找出的强连通分量中,能到达的最小的时间戳。

    在 DFS 的过程中,将经过的点塞进一个栈里面。一旦发现 (dfn_u=low_u) 就一直弹栈直至弹出结点 (u),弹出的这些点就构成了一个强连通分量。

    然后考虑如何求出 (low_u),枚举 (u) 的每条出边 ((u,v))

    • 结点 (v) 未遍历过,先递归处理该点,这样 ((u,v)) 就成了树边,然后 (low_ugetsmin{low_u,low_v})
    • 结点 (v) 已遍历过。
      • 结点 (v) 处在一个已找出的强连通分量中,根据定义直接跳过。
      • 结点 (v) 处在未找出的强连通分量中,这样 ((u,v)) 就成了非树边,同样地,(low_ugetsmin{low_u,low_v})请注意,这种情况下 (oldsymbol v) 一定能走到 (oldsymbol u)。否则肯定不能走到 DFS 生成树上的 LCA 处,也就先变成强连通分量了,与前提不符。

    (low) 数组其实是在找一条向上((dfn) 递减)的路径,而两个强连通分量是不可能有公共点的,所以我们才会有经过边的限制。

    就像这样,(1 o 2 o 3 o 4)(4) 经过红色的横叉边到了 (dfn) 更小的点 (3) 上,而 (3) 又可以合并到 (2) 上,(4) 又肯定是从 (2) 下来的,所以 (4) 属于 (2) 的强连通分量。

    但是还有一个问题,(low) 数组有时会不能更新完全,怎么办呢?

    按照 (1 o 2 o 3) 的顺序走,假设先遍历到了绿色边,再遍历到了红色边,可以发现,(low_3) 没有更新完全的原因是 (low_2) 没有更新完全。所以问题出在已遍历过的情况中。

    但其实是没有关系的,(low) 数组的目的仅仅是判断当前强连通分量是否能够向上合并。就算没有更新完全,如果能够向上合并的话,(dfn_{low_u}) 肯定还是小于 (dfn_u) 的。

    所以可以将取 (min) 中的 (low_v) 换成 (dfn_v)

    那么算法的正确性就很显然了,在合法的情况下(能够向上合并)尽可能将当前强连通分量扩大。

    割点

    可以发现对无向图不可能存在横叉边。并且树边和前向边都是返祖边,返祖边仅由它们两种边组成。

    随便钦定一个根节点,判断它是否为割点很简单:是否存在两棵及以上的子树。

    诶那对每个点都这么做一下不就好了?对不起,每次需要遍历子树,两个儿子可能是连一起的。

    同样地,(dfn) 表示时间戳。但 (low) 的定义要换一下,改成最多经过一条非树边或反向经过一条树边,能到达的最小的时间戳。

    这么说也许不是很准确,其实就是子树中的点能直接到达的子树外的点的最小时间戳。

    因为子树内部可以随便走来走去。

    如果存在 ((u,v)) 其中 (u)(v) 的父亲使得 (low_v=low_u) 则说明以 (v) 为根子树中的点如果不通过 (u)一定不能向上走了,说明 (u) 是割点。

    如果点 (u) 的所有树边都不能说明 (u) 是割点,那么 (u) 一定不是割点。因为子树内所有的点都能走到子树内某些点,然后通过条边连出去。

    所以我们仅仅需要将所有边判断一下。同样地,这里的更新咋写?

    树边 (low_u=min{low_v,low_u})?对。

    非树边 (low_u=min{low_u,low_v})?错!

    请注意这里是割,一个点被割掉后包含该点的所有边都被删掉了!如果我们恰好经过一条非树边或反向经过一条树边回到了某个割点,然后再经过一条非树边或反向经过一条树边回到更上面的点……显然是不合法的!

    所以我们仅能经过条非树边或反向经过一条树边回上去(把 (low_v) 改成 (dfn_v)),这样就算回到了割点,也不能向上跳了,并不会导致割点的判断错误。而多于一条边就可能出错了!

    显然这样 (low) 的更新是完全的。

    割边

    (low) 的定义和割点稍有不同,不能反向经过树边

    • 首先非树边不可能成为割边。
    • 对于一条边 ((u,v)) 其中 (u)(v) 的父亲,如果 (low_v>dfn_u) 则说明 ((u,v)) 是割边。

    不取等于是因为 (u) 在子树外。其余原理分析类似,不赘述。

    唯一需要注意的是反向经过一条树边的处理。当出现重边时,我们会误判。所以当第二次遇到回到父亲的边时,直接将父亲的 (dfn) 赋给当前的 (low) 即可。

    边双联通分量

    先讲这个是因为比较简单。

    找出所有的割边并割掉,剩下的连通块就是所有的边双联通分量。

    点双联通分量

    其实就是每次删除割点,分成若干个不连通的子图,然后将割点重新加入每个子图,但不合并子图。如果找不到割点就说明是点双联通分量了。

    当然不能直接这么干。在找割点的过程中,将经过的点塞进一个栈里面,一旦发现点 (u) 的儿子点 (v) 满足 (low_v=low_u) 就不断弹栈直到弹出点 (v),再加上点 (u) 就是一个点双联通分量。

    一个割点至少存在于两个点双联通分量中。

    圆方树

    原图中每个点双都对应一个方点,每个点都对应一个圆点,每个方点向其点双中的点对应的圆点连边。

    比如说菊花图和链的圆方树分别长这样:

    一些性质:

    • 因为割点的数量小于 (n),所以圆方树点数小于 (2n)
    • 方点之间不可能存在边,而圆点之间可能存在边。
    • 方点的度即点双连通块的大小。
  • 相关阅读:
    【java】i++与++i、i--运算
    配置ssh框架启动tomcat服务器报异常Unable to create requested service [org.hibernate.engine.jdbc.env.spi.JdbcEnvironment]
    jsp页面第一句话报这个错Syntax error, insert "}" to complete
    oracle忘记密码用户名被锁定_解决方案
    关于c#的单例模式,static 变量,下面一篇很不错
    Entity Framework 冲突检测,这一篇我看了比较明了
    关于lambda表达式与使用局部变量的作用域问题,下面这篇不错
    C# SelectMany 的使用
    UML类图 入门 (转载)
    VS code key shortcuts for windows
  • 原文地址:https://www.cnblogs.com/May-2nd/p/13445212.html
Copyright © 2020-2023  润新知