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; }

浙公网安备 33010602011771号