洛谷 - 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; // 程序正常结束
}