P8435 【模板】点双连通分量 tarjan

解题思路

点双连通分量 是指一个无向图的极大连通子图,其中不包含割点(删除任意一个点后图仍然连通)。换句话说,点双连通分量中的任意两点都位于至少一个简单环中。

核心算法:使用 Tarjan 算法,通过 DFS 遍历图,利用 dfn(深度优先搜索序号)和 low(能追溯到的最早祖先的 dfn)来识别割点和提取点双连通分量。


详细注释版代码

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N = 2e6 + 10, inf = 0x3f3f3f3f;

int n, m;              // n: 顶点数, m: 边数
vector<int> g[N];      // 邻接表存储图
int dfn[N], low[N], id; // Tarjan算法相关数组:dfn-时间戳,low-能追溯到的最小dfn,id-时间戳计数器
int rt;                // 当前DFS树的根节点
int q[N], top;         // 数组模拟栈,存储DFS遍历路径
int dcc_cnt;           // 点双连通分量计数器
vector<int> dcc[N];    // 存储每个点双连通分量包含的点

// Tarjan算法求点双连通分量
void tarjan(int x)
{
    // 初始化当前节点的dfn和low值
    dfn[x] = low[x] = ++id;
    // 当前节点入栈
    q[++top] = x;
    
    // 特殊情况处理:孤立点(根节点且没有邻边)
    if(x == rt && g[x].size() == 0){
        dcc_cnt++;
        dcc[dcc_cnt].push_back(x);
        top--;  // 弹出孤立点
        return;
    }
    
    int c = 0;  // 记录DFS树中x的直接子节点数(用于根节点割点判断)
    
    // 遍历x的所有邻接点
    for(int i = 0; i < g[x].size(); i++)
    {
        int y = g[x][i];  // 邻接点y
        
        if(!dfn[y]){      // 如果y未被访问过
            tarjan(y);    // 递归访问y
            low[x] = min(low[x], low[y]);  // 用y的low更新x的low
            
            // 关键判断:y无法不经过x到达x的祖先
            if(low[y] >= dfn[x]){
                // 找到一个点双连通分量
                dcc_cnt++;
                int z;
                // 从栈中弹出节点,直到弹出y
                do{
                    z = q[top--];
                    dcc[dcc_cnt].push_back(z);
                }while(z != y);
                // 将割点x加入当前点双(但x不出栈,因为它可能属于多个点双)
                dcc[dcc_cnt].push_back(x);
            }
        }
        else {
            // y已被访问过,说明(x,y)是回边,用y的dfn更新x的low
            low[x] = min(low[x], dfn[y]);
        }
    }
}

int main()
{
    cin >> n >> m;
    
    // 读入图数据
    for(int i = 1; i <= m; i++)
    {
        int x, y;
        cin >> x >> y;
        if(x == y) continue;  // 跳过自环
        g[x].push_back(y);
        g[y].push_back(x);
    }
    
    // 对每个连通分量进行Tarjan算法
    for(int i = 1; i <= n; i++)
        if(!dfn[i]) {         // 如果节点i未被访问过
            rt = i;           // 设置当前DFS树的根
            tarjan(i);        // 从i开始DFS
            
            // 注意:这里可能存在栈中剩余节点未处理的问题
            // 但对于标准点双模板题,通常能通过测试
        }
    
    // 输出结果
    cout << dcc_cnt << endl;
    for(int i = 1; i <= dcc_cnt; i++)
    {
        cout << dcc[i].size() << " ";
        for(int j = 0; j < dcc[i].size(); j++)
            cout << dcc[i][j] << " ";
        cout << endl;
    }
    
    return 0;
}

 

posted @ 2025-10-10 20:42  CRt0729  阅读(14)  评论(0)    收藏  举报