Title

(模板)割点

割点(Tarjan)

题意概述

给出一个 \(n\) 个点,\(m\) 条边的无向图,求图的割点。

思路

  1. \(dfn[i]\)\(i\)是第几个被访问到的
  2. \(low[i]\): \(i\)点通过一条返祖边能回到的$dfn值最小的点是哪一个
  3. 对于点\(i\), 它的所有儿子中不存在能通过返祖边或者横插边越过它的,那么这个点就是一个割点
  4. 也就是\((u, v)\), \(u\)是父亲,\(v\)是儿子,一旦有\(dfn(u) <= low(v)\),那么\(u\)就是一个割点

代码

#include <bits/stdc++.h>

const int maxn = 2e4 + 10, inf = 2e5 + 10;
bool ifcut[maxn] = {0};
int cnt = 0, n = 0, m = 0, u = 0, v = 0, s= 0;
int head[maxn], low[maxn], dfn[maxn];
struct Edge
{
    int start;
    int terminus;
    int next;
}edge[inf];

void AddEdge(int start, int terminus)
{
    edge[++s].start = start;
    edge[s].terminus = terminus;
    edge[s].next = head[start];
    head[start] = s;
}
int ans = 0;
int root = 0;
void tarjan(int x)
{
    low[x] = dfn[x] = ++cnt;
    int flag = 0;
    for (int i = head[x]; i != -1; i = edge[i].next)
    {
        int y = edge[i].terminus;
        if (dfn[y] == 0)
        {
            tarjan(y);
            low[x] = std::min(low[x], low[y]);
            if (low[y] >= dfn[x])
            {
                flag++; // x如果是根的话,有一个儿子就会满足一次这个条件,flag就把根的儿子数量记录下来了
                if (x != root || flag > 1)
                {
                    ans += (!ifcut[x]);
                    ifcut[x] = true;
                }
            }
        }
        else
        {
            low[x] = std::min(low[x], dfn[y]);
        }
    }
}

int main()
{
    memset(head, -1, sizeof(head));
    memset(ifcut, 0, sizeof(ifcut));
    std::cin >> n >> m;
    for (int i = 1; i <= m; i++)
    {
        std::cin >> u >> v;
        AddEdge(u, v);
        AddEdge(v, u);
    }
    for (int i = 1; i <= n; i++)
    {
        if (dfn[i] == 0) root = i, tarjan(i);
    }

    std::cout << ans << "\n";
    for (int i = 1; i <= n; i++)
    {
        if (ifcut[i])
        {
            std::cout << i << " ";
        }   
    }
    // for (int i = 1; i <= n; i++)
    // {
    //     std::cout << dfn[i] << " ";
    // }
    return 0;
}
posted @ 2024-09-26 21:36  栗悟饭与龟功気波  阅读(24)  评论(0)    收藏  举报