「2024 - 暑假 - Day-1 提高笔记-割点(割边)」

割点(割边)

前置知识

首先是一些简单的基础。

  1. 连通分量:在无向图中其实就是相当于连通块。
  2. dfs 序:利用 dfs 在树上遍历的过程。
  3. 割点:去除这个点后这个图的连通块又增加了。
  4. 割边:去除这个边后这个图的连通块又增加了。

实现思路

如果我们在一个连通分量里面对任意一个点做 dfs 序,那么,会产生一个深度优先生成树 T。我们可以很显然的得到定理1.

定理1:如果 sT 的根节点,且 s 有大于等于 2 个子节点,那么 s 为割点。

同时我们仔细想一想,如果一个一般的点 u,在什么情况下满足是一个割点呢?对,是当下面的子节点没有能退回 u 的父节点的时候。我们可以得到定理2

定理2:如果 T 的非根节点 u,当且仅当 u 存在一个子节点 vv 及其后代没有退回边连 u 的祖先,此时 u 为割点。

代码思路

根据定理1定理2,我们应该如何编程实现呢?

设对于一个节点 u,其直接后代为 v

  1. 对于我们的 dfs 序,用 num 数组来记录对每个点的访问顺序,num 随着 dfs 的加深不断增大。
  2. 对于我们的退回思路,定义 low[v] 表示 v 及其后代能回连到祖先的 num 值。如果 low[v] >= num[u],就说明 v 一定没有回连的边,那么 u 一定是割点。这就是 定理2

有趣的割边

对于以上的思路:如果将 low[v] >= num[u] 改为 low[v] > num[u],这就是求割边的代码,为什么?原因在于如果 u 的后代最多只能回退到 v,就说明了边 (u,v) 就是一条割边。

代码实现

P3388 【模板】割点(割顶)

#include<iostream>
#include<vector>
using namespace std;

const int MAXN = 2 * 1e4 + 5;
int low[MAXN], num[MAXN];
int tot = 0, n, m, ans = 0;
bool iscut[MAXN];
vector<int> G[MAXN];

void dfs(int u, int fa) {
    low[u] = num[u] = ++tot;
    int child = 0;
    for (int v : G[u]) {
        if (!num[v]) {
            child++;
            dfs(v, u);
            low[u] = min(low[u], low[v]);//用后代的值更新一下
            if (low[v] >= num[u] && u != fa) iscut[u] = true;//定理2
        }
		else if (num[v] < num[u] && v != fa) {//处理回连边
            low[u] = min(low[u], num[v]);
        }
    }
    if (u == fa && child >= 2) iscut[u] = true;//定理1
}

int main() {
    cin >> n >> m;
    for (int i = 1, u, v; i <= m; i++) {
        cin >> u >> v;
        G[u].push_back(v);
        G[v].push_back(u);
    }
    for (int i = 1; i <= n; i++) {
        if (!num[i]) dfs(i, i);//题目中说图可能不联通
    }
    for (int i = 1; i <= n; i++) if (iscut[i]) ans++;
    cout << ans << '\n';
    for (int i = 1; i <= n; i++) if (iscut[i]) cout << i << ' ';
    return 0;
}
posted @   To_Carpe_Diem  阅读(45)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!
点击右上角即可分享
微信分享提示