洛谷 - P1656 炸铁路

模板题

// Problem: P1656 炸铁路
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/P1656
// Memory Limit: 125 MB
// Time Limit: 1000 ms
// 
// Powered by CP Editor (https://cpeditor.org)


#include <cstdio>       // 引入标准输入输出库
#include <algorithm>    // 引入算法库,用于排序等操作
#include <cstring>      // 引入字符串处理库,用于内存初始化等
using namespace std;    // 使用标准命名空间

// 定义常量:N为最大节点数,M为最大边数(无向图每条边存储两次)
const int N=155,M=2*(5e3+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;

// 存储桥的结构体数组,每个桥包含两个端点u和v
struct Bridge{
    int u,v;
}bridge[M];
int cnt;                // cnt用于记录桥的数量

// 向邻接表中添加一条从a到b的边
// a: 起点, b: 终点
void add(int a,int b){
    e[idx] = b;         // 存储当前边的终点
    ne[idx] = h[a];     // 将当前边的下一条边指向a原来的第一条边
    h[a] = idx++;       // 更新a的第一条边为当前边,边编号递增
}

// Tarjan算法核心函数,用于寻找桥
// u: 当前访问的节点, pre: 到达当前节点的边的编号(用于避免回退到父节点)
void tarjan(int u,int pre){
    dfn[u] = low[u] = ++timestamp;  // 初始化发现时间和最早可达时间为当前时间戳
    
    // 遍历当前节点的所有邻接边
    // i从h[u]开始,沿着ne[i]遍历所有边,直到i=-1(表示没有更多边)
    for(int i=h[u];~i;i=ne[i]){
        int v = e[i];   // 获取当前边的终点v
        
        if(!dfn[v]){    // 如果v未被访问过(未分配发现时间)
            tarjan(v,i);    // 递归访问v,传入当前边的编号i作为pre
            
            // 更新当前节点u的low值,取u和v的low值中的较小者
            low[u] = min(low[u], low[v]);
            
            // 如果v的low值大于u的发现时间,说明u-v是桥
            if(low[v] > dfn[u]){
                bridge[cnt++] = {u, v};  // 记录该桥
            }
        }
        // 如果v已被访问过,且当前边不是来时边的反向边(避免父子节点互访)
        // pre^1是因为无向图中边成对存储,异或1可得到反向边的编号
        else if(i != (pre^1)){
            // 更新当前节点u的low值,取u的low值和v的发现时间中的较小者
            low[u] = min(low[u], dfn[v]);
        }
    }
}

int main(){
    memset(h, -1, sizeof h);  // 初始化邻接表头数组为-1,表示初始时没有边
    
    scanf("%d%d", &n, &m);    // 读取节点数n和边数m
    
    // 循环读取m条边
    for(int i=1,a,b;i<=m;i++){
        scanf("%d%d", &a, &b);  // 读取边的两个端点a和b
        add(a,b);               // 添加a到b的边
        add(b,a);               // 添加b到a的边(无向图双向存储)
    }
    
    // 对所有未访问过的节点执行Tarjan算法(处理非连通图情况)
    for(int i=1;i<=n;i++){
        if(!dfn[i]){            // 如果节点i未被访问过
            tarjan(i, -1);      // 从节点i开始遍历,pre为-1表示没有前驱边
        }
    }
    
    // 对找到的桥进行排序,按u从小到大排序,u相同则按v从小到大排序
    sort(bridge, bridge + cnt, [](const Bridge &a, const Bridge &b){
        if(a.u != b.u) return a.u < b.u;  // 先比较u
        else return a.v < b.v;            // u相同则比较v
    });
    
    // 输出所有桥的两个端点
    for(int i=0;i<cnt;i++){
        printf("%d %d\n", bridge[i].u, bridge[i].v);
    }
    
    return 0;   // 程序正常结束
}

posted @ 2025-08-20 16:52  九三青梧  阅读(7)  评论(0)    收藏  举报