洛谷 - P2863 [USACO06JAN] The Cow Prom S
模板题
// Problem: P2863 [USACO06JAN] The Cow Prom S
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/P2863
// Memory Limit: 128 MB
// Time Limit: 1000 ms
//
// Powered by CP Editor (https://cpeditor.org)
#include <cstdio> // 用于输入输出函数,如scanf、printf
#include <cstring> // 用于内存操作函数,如memset
#include <algorithm> // 用于算法函数,如min
using namespace std; // 引入标准命名空间,避免使用std::前缀
// 定义常量:N为最大节点数,M为最大边数
const int N = 2e4 + 10, M = 5e4 + 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)相关变量:
// scc[]存储每个节点所属的强连通分量编号
// size[]存储每个强连通分量的大小
// cnt为强连通分量的数量
int scc[N], size[N], cnt;
// 向图中添加一条从a到b的有向边
// a: 起点, b: 终点
void add(int a, int b) {
e[idx] = b; // 存储边的终点
ne[idx] = h[a]; // 将新边的下一条边指向当前a的第一条边
h[a] = idx++; // 更新a的第一条边为当前边,边计数加1
}
// Tarjan算法核心函数,用于寻找强连通分量
// u: 当前处理的节点
void tarjan(int u) {
// 初始化当前节点的dfn和low值为当前时间戳,并将时间戳加1
dfn[u] = low[u] = ++timestamp;
// 将当前节点入栈,并标记为在栈中
stk[++top] = u;
instk[u] = true;
// 遍历当前节点的所有邻接边
for (int i = h[u]; ~i; i = ne[i]) { // ~i 等价于 i != -1
int v = e[i]; // 获取边的终点v
if (!dfn[v]) { // 如果v未被访问过
tarjan(v); // 递归处理v
// 更新当前节点的low值为其自身low值与v的low值的最小值
low[u] = min(low[u], low[v]);
}
// 如果v已被访问且在栈中,说明形成环,更新当前节点的low值
else if (instk[v]) {
low[u] = min(low[u], dfn[v]);
}
}
// 当节点u的dfn值等于low值时,说明u是一个强连通分量的根
if (dfn[u] == low[u]) {
cnt++; // 强连通分量数量加1
int v; // 用于临时存储出栈的节点
// 将栈中从u开始的所有节点弹出,这些节点构成一个强连通分量
do {
v = stk[top--]; // 栈顶节点出栈
instk[v] = false; // 标记为不在栈中
scc[v] = cnt; // 记录v所属的强连通分量编号
size[cnt]++; // 该强连通分量的大小加1
} while (v != u); // 直到弹出的节点是u为止
}
}
int main() {
// 读取节点数和边数
scanf("%d%d", &n, &m);
// 初始化邻接表头为-1(表示无邻接边)
memset(h, -1, sizeof h);
// 读取m条边并添加到图中
for (int i = 1, a, b; i <= m; i++) {
scanf("%d%d", &a, &b); // 读取边的起点a和终点b
add(a, b); // 添加边
}
// 对所有未访问的节点执行Tarjan算法
for (int i = 1; i <= n; i++) {
if (!dfn[i]) { // 如果节点i未被访问
tarjan(i); // 处理节点i
}
}
// 统计大小大于1的强连通分量的数量
int ans = 0;
for (int i = 1; i <= cnt; i++) {
if (size[i] > 1) { // 如果强连通分量i的大小大于1
ans++; // 计数加1
}
}
// 输出结果
printf("%d\n", ans);
return 0;
}