P3388 【模板】割点(割顶) tarjan

解题思路

割点(割顶):在一个无向图中,如果删除某个顶点以及与之相连的所有边后,图的连通分量数量增加,则该顶点称为割点。

Tarjan算法求割点的核心思想:

  1. 使用深度优先搜索遍历图

  2. 维护两个数组:

    • dfn[i]:顶点i的深度优先搜索遍历序号(时间戳)

    • low[i]:顶点i能够回溯到的最早的祖先节点的dfn值

  3. 割点的判断条件:

    • 对于根节点:如果有2个或以上的子树,则它是割点

    • 对于非根节点:如果存在子节点y满足low[y] >= dfn[x],则x是割点

      #include<bits/stdc++.h>
      #define ll long long
      using namespace std;
      const int N = 2e5 + 10,inf = 0x3f3f3f3f;
      vector<int> g[N];  // 邻接表存储图
      int dfn[N],low[N],f[N],id;  // dfn:时间戳,low:能回溯到的最小dfn,f:父节点,id:时间戳计数器
      int vis[N];  // 标记节点是否为割点
      int ans;     // 割点数量
      int n,m;     // 顶点数,边数
      
      void tarjan(int x,int fa)  // Tarjan算法,x:当前节点,fa:父节点
      {
          dfn[x] = low[x] = ++id;  // 初始化当前节点的dfn和low值
          f[x] = fa;               // 记录父节点
          int cnt = 0;             // 记录当前节点的子节点数量(用于判断根节点是否为割点)
          
          for(int i = 0; i < g[x].size(); i++)
          {
              int y = g[x][i];  // 邻接节点y
              if(dfn[y] == 0){  // 如果y未被访问过(树边)
                  cnt++;        // 子节点计数+1
                  tarjan(y,x);  // 递归遍历子节点
                  
                  low[x] = min(low[x],low[y]);  // 更新当前节点的low值
                  
                  // 判断割点条件:
                  if(f[x] == 0){  // 如果x是根节点
                      if(cnt >= 2) vis[x] = 1;  // 根节点有>=2个子树则是割点
                  }
                  else if(low[y] >= dfn[x]) vis[x] = 1;  // 非根节点:存在子节点y满足low[y]>=dfn[x]
              }
              else if(y != f[x]) low[x] = min(low[x],dfn[y]);  // 回边,更新low值
          }
      }
      
      int main()
      {
          cin >> n >> m;
          // 读入图数据,构建无向图
          for(int i = 1; i <= m; i++)
          {
              int x,y; cin >> x >> y;
              g[x].push_back(y);
              g[y].push_back(x);
          }
          
          // 遍历所有连通分量(因为图可能不连通)
          for(int i = 1; i <= n; i++)
          {
              if(dfn[i] == 0)  // 如果节点i未被访问过
                  tarjan(i,0); // 从i开始Tarjan算法,父节点设为0(表示根节点)
          }
          
          // 统计割点数量
          for(int i = 1; i <= n; i++)
              if(vis[i]) ans++;
          
          // 输出结果
          cout << ans << endl;
          for(int i = 1; i <= n; i++)
              if(vis[i])
                  cout << i << " ";
          return 0;
      }

       

posted @ 2025-10-09 21:07  CRt0729  阅读(13)  评论(0)    收藏  举报