洛谷 - B3609 [图论与代数结构 701] 强连通分量
模板题
#include <cstdio> // 包含标准输入输出函数,如scanf、printf
#include <cstring> // 包含字符串处理函数,如memset
#include <algorithm> // 包含算法函数,如sort
#include <vector> // 包含vector容器
using namespace std; // 引入标准命名空间,避免使用std::前缀
// 定义常量,N为最大节点数,M为最大边数
const int N = 1e4 + 10, M = 1e5 + 10;
int n, m; // n表示节点数,m表示边数
// 邻接表存储图:e[]存储边的终点,ne[]存储下一条边的索引,h[]存储头节点的第一条边索引,idx为边的计数器
int e[M], ne[M], h[N], idx;
// Tarjan算法相关变量:
// dfn[]记录节点被访问的时间戳,low[]记录节点能回溯到的最早时间戳,timestamp为时间戳计数器
int dfn[N], low[N], timestamp;
// stk[]为栈,用于存储当前路径上的节点,top为栈顶指针,instk[]标记节点是否在栈中
int stk[N], top;
bool instk[N];
// scc[]记录每个节点所属的强连通分量编号,size[]记录每个强连通分量的大小,cnt为强连通分量计数器
int scc[N], size[N], cnt;
// 向邻接表中添加一条从a到b的有向边
void add(int a, int b) {
e[idx] = b; // 存储边的终点
ne[idx] = h[a]; // 存储下一条边的索引
h[a] = idx++; // 更新头节点的第一条边索引,并递增边计数器
}
// Tarjan算法核心函数,用于查找强连通分量
void tarjan(int u) {
dfn[u] = low[u] = ++timestamp; // 初始化时间戳,dfn和low均为当前时间戳
stk[++top] = u; // 将当前节点入栈
instk[u] = true; // 标记当前节点在栈中
// 遍历当前节点的所有邻接边
for (int i = h[u]; ~i; i = ne[i]) { // ~i 等价于 i != -1,遍历直到没有下一条边
int v = e[i]; // 获取邻接边的终点
if (!dfn[v]) { // 如果邻接节点未被访问过
tarjan(v); // 递归访问邻接节点
// 更新当前节点的low值,取当前low值和邻接节点的low值中的较小者
low[u] = min(low[u], low[v]);
} else if (instk[v]) { // 如果邻接节点已被访问且在栈中(说明在当前路径上)
// 更新当前节点的low值,取当前low值和邻接节点的dfn值中的较小者
low[u] = min(low[u], dfn[v]);
}
}
// 如果当前节点的dfn值等于low值,说明找到了一个强连通分量的根节点
if (dfn[u] == low[u]) {
cnt++; // 强连通分量计数器加1
int v; // 用于临时存储出栈的节点
do {
v = stk[top--]; // 栈顶节点出栈
instk[v] = false; // 标记节点不在栈中
scc[v] = cnt; // 记录节点所属的强连通分量编号
size[cnt]++; // 增加该强连通分量的大小
} while (v != u); // 直到当前节点出栈,完成一个强连通分量的收集
}
}
int main() {
scanf("%d%d", &n, &m); // 读取节点数和边数
memset(h, -1, sizeof h); // 初始化邻接表头节点为-1(表示无后续边)
// 读取每条边并添加到邻接表
for (int i = 1, a, b; i <= m; i++) {
scanf("%d%d", &a, &b); // 读取边的起点和终点
add(a, b); // 添加边
}
// 对所有未访问的节点执行Tarjan算法
for (int i = 1; i <= n; i++) {
if (!dfn[i]) { // 如果节点未被访问
tarjan(i); // 执行Tarjan算法
}
}
printf("%d\n", cnt); // 输出强连通分量的数量
// 按强连通分量整理节点
vector<vector<int>> ans(cnt + 1); // ans[i]存储第i个强连通分量的所有节点
for (int i = 1; i <= n; i++) {
ans[scc[i]].push_back(i); // 将节点i加入其所属的强连通分量列表
}
// 按每个强连通分量的第一个节点(最小节点)升序排序
sort(ans.begin() + 1, ans.end(), [](const vector<int>& a, const vector<int>& b) {
return a[0] < b[0]; // 比较两个强连通分量的第一个节点
});
// 输出每个强连通分量的节点
for (int i = 1; i <= cnt; i++) {
for (int j = 0; j < ans[i].size(); j++) {
printf("%d ", ans[i][j]); // 输出强连通分量中的每个节点
}
puts(""); // 每个强连通分量输出完毕后换行
}
return 0;
}