连通性相关
一些概念
连通:无向图中任意两点都可以互相到达。
强连通:有向图中任意两点都可以互相到达。
连通分量:无向图的极大连通子图。
强连通分量:有向图的极大强连通子图。
DFS 生成树:对一张图进行深度优先遍历得到的生成树。
树边:在 DFS 生成树上的边。
前向边:由子树的根连向子树内的非树边。
返祖边:由结点连向其祖先的边。
横叉边:除上面三种以外的边,也就是两棵不交子树之间的边。
割点:无向图删除某个点及其为端点的所有边后,若图的连通块数量增加,则该点是图的一个割点。
割边(桥):无向图删除某条边后,若图的连通块数量增加,则该边是图的一条割边。
点双连通图:不存在割点的无向连通图。
边双连通图:不存在割边的无向连通图。
点双连通分量:无向图的极大点双连通子图。
边双连通分量:无向图的极大边双连通子图。
核心思路
以 DFS 生成树为基础,通过维护点的信息,判断当前搜索的位置能否与祖先连通。
缩点(求强连通分量)
对于每个点 \(u\),记录两个信息 \(dfn_u\) 和 \(low_u\)。
\(dfn_u\) 表示时间戳,即 \(u\) 是第几个被遍历到的。\(low_u\) 表示从 \(u\) 开始,不经过已经找出的强连通分量所能到达的最小时间戳,因为从外界进入已经找出的强连通分量肯定无法回到原点。
在 DFS 的过程中,将经过的点塞进栈里面,递归完后一旦发现 \(low_u=dfn_u\) 就说明 \(u\) 无法离开以其为根的子树。此时一直弹栈直至弹出 \(u\),弹出的这些点就构成了一个强连通分量。
重点在于更新 \(low_u\) 的过程,枚举 \(u\) 的每条出边 \((u,v)\):
- \(v\) 未遍历过即 \((u,v)\) 为树边。先递归处理 \(v\),然后令 \(low_u\gets\min(low_u,low_v)\)。
- \(v\) 已遍历过:
- \(v\) 位于已经找出的强连通分量中,直接跳过。
- \(v\) 不在已经找出的强连通分量中,同样令 \(low_u\gets\min(low_u,low_v)\)。因为 \(v\) 一定能走到两点的 LCA 处进而走到 \(u\),否则 \(v\) 在 LCA 对应子树的根处肯定形成了一个强连通分量,与前提不符。
但这样存在一个问题,\(low\) 有时无法更新完全,如下图:

按照 \(1\to 2\to 3\) 的顺序,假设先遍历绿色边再遍历红色边,那么就会在 \(low_2\) 没有更新完全时就去递归处理 \(3\) 号点,进而导致 \(low_3\) 无法更新完全。
这个问题不能避免,具体可以考虑某点的两棵子树内都有通往更上面的返祖边,而哪边的 \(low\) 更小我们是无法预知的,不能通过调整出边顺序解决。所幸我们只关心 \(dfn\) 和 \(low\) 的相对大小。
出于严谨,给 \(low\) 的定义增加最多经过一条非树边的条件,然后将第二处更新改为 \(low_u\gets\min(low_u,dfn_v)\) 就没有问题了。
割点
把无向图看成有向图,容易发现不存在横叉边,返祖边由树边和前向边组成。
随便钦定一个根结点,如果其存在两棵及以上的子树说明它是割点。然后考虑剩下的点。
\(dfn\) 定义不变,\(low\) 改成最多经过一条返祖边(反向经过一条前向边或树边)。如果存在边 \((u,v)\) 其中 \(u\) 是 \(v\) 的父亲满足 \(low_v=dfn_u\) 就说明 \(v\) 子树中的点如果不经过 \(u\) 就无法与祖先连通了,即 \(u\) 是一个割点。这里也能看出特殊处理根的原因是根没有祖先。
需要注意的是,删除点的同时会删除以其为端点的所有边,所以经过返祖边的更新不能写 \(low_v\) 必须写 \(dfn_v\),否则相当于允许中途经过割点了会判断错误。
割边
\(dfn\) 定义不变,\(low\) 改成最多反向经过一条前向边。不能反向经过树边了,因为我们要判断的就是树边能否不经过。
非树边不可能是割边。如果树边 \((u,v)\) 其中 \(u\) 是 \(v\) 的父亲满足 \(low_v>dfn_u\) 就说明 \(v\) 子树中的点如果不经过 \((v,u)\) 就无法与祖先连通了,即 \((u,v)\) 是一条割边。
需要注意反向经过一条树边的处理,出现重边时要特判一下。
因为删的是边所以反向经过一条前向边的更新可以写成 \(low_v\)。
点双连通分量
每次删除割点,将当前图分成若干个不连通的子图,然后将割点重新加入每个子图。如果找不到割点就说明当前图已经是原图的一个点双连通分量了。
但是具体实现没必要这么麻烦,因为连通分量都是嵌套在子树中的,一样用栈维护。当 \(low_v=dfn_u\) 就一直弹栈直至弹出 \(v\),弹出的这些点加上 \(u\) 就构成了一个点双连通分量。
根不用特判直接当成割点做,因为无论如何都要结束了。孤立点也是一个点双连通分量,要特判。如果有自环最好去掉,方便用是否有连边判断孤立点。
边双连通分量
把割边标记为不能经过就可以分割出所有边双连通分量了。

浙公网安备 33010602011771号