割点
割点(Articulation Point)
在图论中,割点(Articulation Point)是指在一个无向图中,如果删除某个节点及其关联的边会导致图的连通分量数量增加,那么这个节点就被称为割点。换句话说,割点是图中的一个节点,删除它会使图变得不连通或减少连通分量的数量。
性质
- 连通性:删除割点会使得图的连通性降低,即原本连通的节点变得不连通。
- 无向图:割点的概念主要应用于无向图。
- 割点的检测:可以使用Tarjan算法来检测图中的割点。
Tarjan算法检测割点
Tarjan算法是一种深度优先搜索(DFS)算法,用于检测图中的割点和强连通分量。以下是Tarjan算法检测割点的基本步骤:
-
初始化:
- 为每个节点设置一个时间戳(
dfn
)和一个追溯值(low
)。 - 使用一个栈来记录访问的节点。
- 为每个节点设置一个时间戳(
-
DFS遍历:
- 从任意一个节点开始进行DFS遍历。
- 对于每个节点,记录其访问时间戳(
dfn
)和追溯值(low
)。 - 对于每个节点的邻接节点,如果邻接节点未被访问过,则递归访问,并更新当前节点的追溯值(
low
)。 - 如果邻接节点的追溯值(
low
)大于等于当前节点的时间戳(dfn
),则当前节点是割点。
-
割点判定:
- 如果
low[v] >= dfn[u]
,则节点u
是割点。
- 如果
代码:
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N = 2e5 + 10, mod = 998244353;
typedef long long ll;
typedef pair<int, int> PII;
int T;
int dfn[N], low[N], n, m, idx; // dfn: 时间戳, low: 追溯值, n: 节点数, m: 边数, idx: 时间戳计数器
vector<int> g[N]; // 图的邻接表表示
int cut[N]; // 记录节点是否为割点
int ans = 0; // 割点的数量
// Tarjan算法检测割点
void Tarjan(int u, int p) {
dfn[u] = low[u] = ++idx; // 初始化时间戳和追溯值
int sz = 0; // 记录子树的数量
for (auto v : g[u]) {
if (!dfn[v]) { // 如果节点v未访问过
Tarjan(v, u); // 递归访问v
sz++; // 子树数量加1
low[u] = min(low[u], low[v]); // 更新追溯值
if (low[v] >= dfn[u]) cut[u] = 1; // 如果v的追溯值大于等于u的时间戳,则u是割点
} else if (v != p) { // 如果v不是父节点
low[u] = min(low[u], dfn[v]); // 更新追溯值
}
}
if (p == 0 && sz <= 1) cut[u] = 0; // 如果u是根节点且子树数量小于等于1,则u不是割点
ans += cut[u]; // 更新割点数量
}
signed main() {
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
cin >> n >> m; // 读取节点数和边数
for (int i = 1; i <= m; i++) {
int u, v;
cin >> u >> v; // 读取边
g[u].push_back(v); // 添加边到邻接表
g[v].push_back(u); // 无向图,双向添加
}
for (int i = 1; i <= n; i++) {
if (!dfn[i]) Tarjan(i, 0); // 对每个未访问的节点运行Tarjan算法
}
cout << ans << endl; // 输出割点的数量
for (int i = 1; i <= n; i++) {
if (cut[i]) cout << i << " "; // 输出所有割点
}
return 0;
}