原题地址:https://www.luogu.org/problemnew/show/P3388
题目背景
割点
题目描述
给出一个nn个点,mm条边的无向图,求图的割点。
输入输出格式
输入格式:
第一行输入n,mn,m
下面mm行每行输入x,yx,y表示xx到yy有一条边
输出格式:
第一行输出割点个数
第二行按照节点编号从小到大输出节点,用空格隔开
输入输出样例
输入样例#1:
6 7
1 2
1 3
1 4
2 5
3 5
4 5
5 6
输出样例#1:
1
5
说明
对于全部数据,n \le 20000n≤20000,m \le 100000m≤100000
点的编号均大于00小于等于nn。
tarjan图不一定联通。
思路及代码如下
#include <iostream>
#include <vector>
#include <cstring>
#include <cmath>
#include <set>
using namespace std;
vector<int> g[20005];
bool vis[20005];
int dfn[20005];
int low[20005];
int cnt;
set<int> ans; //存割点
void tarjan(int x, int s) //s为根节点的下标
{
cnt ++;
dfn[x] = cnt;
low[x] = cnt;
int child = 0;
for(int i = 0; i < g[x].size(); i ++){
int r = g[x][i];
if(!vis[r]){ //第一次访问到r
vis[r] = true;
tarjan(r, s);
low[x] = min(low[x], low[r]); //取最小
if(x == s){ //如果是根节点
child ++; //统计子树数量
}
else {
if(dfn[x] <= low[r]) //如果不是根节点,要能够回到更前面的边
ans.insert(x); //否则此节点就是割点
}
}
else { //访问过了,dfn[r]肯定小于dfn[x]
low[x] = min(low[x], dfn[r]); //所有取一次min
}
}
if(child >= 2 && x == s){ //如果是根节点且子树数量大于1
ans.insert(x); //则是割点
}
}
int main()
{
int n, m;
while(cin >> n >> m){
for(int i = 0; i < m; i ++){
int x, y;
cin >> x >> y;
g[x].push_back(y);
g[y].push_back(x);
}
memset(vis, 0, sizeof(vis));
for(int i = 1; i <= n; i ++){ //图不一定是连通图
cnt = 0;
if(!vis[i]){
vis[i] = true;
tarjan(i, i);
}
}
cout << ans.size() << endl;
bool first = true;
for(auto it = ans.begin(); it != ans.end(); it ++){
if(first)
first = false;
else
cout << " ";
cout << *it;
}
cout << endl;
for(int i = 1; i <= n; i ++)
g[i].clear();
ans.clear();
}
return 0;
}