【图论】割点(割顶)

前置定义

有无向图 \(G = (V, E).\)

  • 无向图的 DFS 树:从某一点 \(root\) 开始 DFS,访问邻点 \(.\) 当搜索到点 \(u\) 时,我们遍历每一条以 \(u\) 为起点的边 \((u, v_i)\),且定义有向边 \(u \longrightarrow v_i.\) 于是 DFS 的过程全部完成之后,所有被定义的有向边就会组成一颗以 \(root\) 为根树,这棵树就是图 \(G\) 的 DFS 树。
  • DFS 序:DFS 过程中,每次搜索到的节点编号组成的序列是为 DFS 序。
  • DFN(DFS number):节点在 DFS 序中的位置,即这个节点是第几个被 DFS 查找到的。下称 节点 \(v\) 的 DFN 为 \({dfn}_v\)

    上图展示了某图的一种 DFS 树以及这种 DFS 序下对应的节点的 DFN 值。

DFS 树的重要性质

定义 \(T(x)\) 为节点 \(x\) 的子树。包括 \(x\)

  • DFN 单调性:任意一个节点 \(u\),满足它的子树中所有的点的 \(dfn\) 值均大于等于它的 \(dfn\) 值。即 \(dfn_v \geq dfn_u[v \in T(u)]\).
  • 祖先后代性:若 \(u \longrightarrow v\) 为非树边,则必有 \(u, v\) 有祖先和后代的关系。

割点判断

所谓割点,是指在删除这个节点之后,整个图的连通块数量有增加,也就是此次删除把某个连通块分割成了两个连通块。所以整个图原来有几个连通块并不重要,重要的是删点后能否分割当前的连通块。只需思考一个连通块内求割点的方法,就可以推广到有几个连通子图的复杂图中。

如果 \(x\) 为割点,把当前连通块从 \(x\) 处分为两部分,一部分是 \(x\) 在 DFS 树上的子树 \(T(x)\),一部分是在 DFS 树上除去 \(T(x)\) 以外的部分 \(T'(x)\)。那么断开以后,产生两个块各自联通,\(T(x)\) 中的任意一点都没有办法不通过 \(x\) 到达 \(T'(x)\) 中的点了。若想不经过 \(x\),必然有一条边 \(u \longrightarrow y\) 可以到达 \(x\) 的祖先节点,绕过 \(x\)。又因为 \(T'(x)\) 自己内部联通,所以只要满足前面的条件就可以通过这条边到达 \(T'(x)\) 中的任意一点。

\(low_u\)\(u\) 仅经过一条非树边能到达的节点中,DFN 值最小的。上面的结论可以进一步表达为:\(low_u < dfn_x\)

如果这个节点为根节点,那么就要判断它的子树数量。如果它有超过一个的子树(即儿子),那么如果删去它,他的子树之间都不会联通了。这个判断可以通过 DFS 时通过树边访问时记录。

代码

#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
int n, m, num, root, cv_cnt;
int dfn[N], low[N];
bool cv_chk[N];
vector<int> g[N];
inline int read(){
    int s = 0, w = 1;
    char ch = getchar();
    while(!isdigit(ch)) { w = (ch == '-' ? -1 : 1); ch = getchar(); }
    while(isdigit(ch)) { s = s * 10 + ch - '0'; ch = getchar(); }
    return s * w;
}
inline void write(int x){
    if(x < 0) { x = -x, putchar('-'); }
    if(x > 9) write(x / 10);
    putchar(x % 10 + '0');
}
void cv(int x){
    int chc = 0;
    dfn[x] = low[x] = ++num;
    for(int y : g[x]){
        if(!dfn[y]){
            cv(y);
            chc++, low[x] = min(low[x], low[y]);
            if(low[y] >= dfn[x]){
                if(x != root || chc > 1){
                    if(!cv_chk[x])
                        cv_cnt++;
                    cv_chk[x] = true;
                }                
            }        
        }else
            low[x] = min(low[x], dfn[y]);
    }
}
int main(){
    n = read(), m = read();
    while(m--){
        int u = read(), v = read();
        g[u].push_back(v), g[v].push_back(u);
    }
    for(int i = 1; i <= n; i++)
        if(!dfn[i])
            root = i, cv(i);
    write(cv_cnt); putchar('\n');
    for(int i = 1; i <= n; i++)
        if(cv_chk[i])
            write(i), putchar(' ');
    return 0;
}
posted @ 2024-05-26 12:33  Prülystic  阅读(32)  评论(0编辑  收藏  举报