tarjan 割点

P3388 【模板】割点(割顶)
割点判定

根结点:若根结点在 DFS 树中有两个或以上的子节点,则去除该根会增加连通分量,故为割点。

非根结点:若存在子节点 v 使得 low[v] >= dfn[u],则去掉 u 后,子树 v 及其后代无法通过返祖边回到 u 的祖先,u 为割点。

注意

访问时间戳 要++cnt

#include <bits/stdc++.h>
using namespace std;

const int MAXN = 20000 + 5;

int n, m;
vector<int> G[MAXN];     // 邻接表存图
int dfn[MAXN], low[MAXN];
bool isCut[MAXN];        // 是否为割点
int cnt = 0;

/**
 * Tarjan 割点算法主函数
 * @param u 当前节点
 * @param fa u 的父节点(用于避免走回父亲的边)
 */
void tarjan(int u, int fa) {
    dfn[u] = low[u] = ++cnt;  // 设置访问时间戳 要++cnt,不能是cnt++, 否则第一个dfn会为0,导致后续if判断是否访问过出错
    int child = 0;                // u 的子节点数量

    for (int v : G[u]) {
        if (!dfn[v]) {
            ++child;
            tarjan(v, u);         // 递归处理子节点 v
            low[u] = min(low[u], low[v]);

            // 如果 u 不是根,并且 v 无法通过其他路径回到 u 或 u 的祖先
            if (fa != -1 && low[v] >= dfn[u]) {
                isCut[u] = true;
            }
        }
        else if (v != fa) {
            // 遇到返祖边,更新 low[u]
            low[u] = min(low[u], dfn[v]);
        }
    }

    // 根节点特判:若有两个及以上子节点,则是割点
    if (fa == -1 && child > 1) {
        isCut[u] = true;
    }
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    cin >> n >> m;

    // 读入图
    for (int i = 0; i < m; i++) {
        int u, v;
        cin >> u >> v;
        G[u].push_back(v);
        G[v].push_back(u);  // 无向图双向加边
    }


    // 多连通块处理:每个未访问点都进行一次 DFS
    for (int i = 1; i <= n; i++) {
        if (!dfn[i]) {
            tarjan(i, -1);  // -1 表示根节点无父亲
        }
    }

    // 输出结果
    vector<int> result;
    for (int i = 1; i <= n; i++) {
        if (isCut[i]) result.push_back(i);
    }

    cout << result.size() << "\n";
    for (int i = 0; i < result.size(); i++) {
        if (i > 0) cout << " ";
        cout << result[i];
    }
    cout << "\n";

    return 0;
}

posted @ 2025-05-14 11:17  katago  阅读(34)  评论(0)    收藏  举报