洛谷 P3388:【模板】割点(割顶)← Tarjan 算法
【题目来源】
https://www.luogu.com.cn/problem/P3388
【题目描述】
给出一个 n 个点,m 条边的无向图,求图的割点。
【输入格式】
第一行输入两个正整数 n,m。
下面 m 行每行输入两个正整数 x,y,表示 x 到 y 有一条边。
【输出格式】
第一行输出割点个数。
第二行按照节点编号从小到大输出节点,用空格隔开。
【输入样例】
6 7
1 2
1 3
1 4
2 5
3 5
4 5
5 6
【输出样例】
1
5
【说明/提示】
对于全部数据,1≤n≤2×10^4,1≤m≤1×10^5。
点的编号均大于 0 小于等于 n。
Tarjan 图不一定连通。
【Tarjan 算法简介】
Tarjan 算法是一种基于深度优先搜索(DFS)的图论算法,由 Robert Tarjan 提出,主要用于求解有向图的强连通分量(SCC),同时也可应用于无向图的桥和割点检测。其核心思想是通过维护两个关键数组(dfn 和 low)及栈结构,实现线性时间复杂度(O(V+E))的求解。
(1)Tarjan算法的两个核心数组 dfn[x] 与 low[x]
dfn[x]:节点 x 的 DFS 访问顺序(时间戳)。
low[x]:节点 x 能回溯到的最小 dfn 值。
(2)数组 low[x] 的更新规则
● 若 x 是 y 的父节点(即 y 通过 DFS 树边被访问),则 low[x]=min(low[x], low[y])。
作用:将子节点 y 能回溯到的最小值传递给父节点 x。
● 若 (x, y) 不是树的边,则 low[x]=min(low[x], dfn[y])。`
作用:通过回边直接回溯到祖先节点 y 的 DFS 序。
【算法代码】
#include <bits/stdc++.h>
using namespace std;const int N=2e4+5;
const int M=2e5+5;
int e[M],ne[M],h[N],idx;
int dfn[N],low[N],timestamp;
bool isCut[N];
int ans[N],cnt;void add(int a,int b) {e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}void tarjan(int u,int root) {dfn[u]=low[u]=++timestamp;int child=0;for(int i=h[u]; i!=-1; i=ne[i]) {int v=e[i];if(!dfn[v]) {child++;tarjan(v,root);low[u]=min(low[u],low[v]);if(low[v]>=dfn[u] && u!=root) {isCut[u]=true;}} else low[u]=min(low[u],dfn[v]);}if(u==root && child>=2) {isCut[root]=true;}
}int main() {memset(h,-1,sizeof h);int n,m;cin>>n>>m;while(m--) {int u,v;cin>>u>>v;add(u,v), add(v,u);}for(int i=1; i<=n; i++) {if(!dfn[i]) tarjan(i,i);}for(int i=1; i<=n; i++) {if(isCut[i]) ans[cnt++]=i;}cout<<cnt<<endl;for(int i=0; i<cnt; i++) {cout<<ans[i]<<" ";}return 0;
}/*
in:
6 7
1 2
1 3
1 4
2 5
3 5
4 5
5 6out:
1
5
*/
【参考文献】
https://blog.csdn.net/hnjzsyjyj/article/details/139796863