洛谷 - 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;
}
posted @ 2025-08-18 16:55  九三青梧  阅读(8)  评论(0)    收藏  举报