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号点去掉,图依旧联通,3号去掉,4号去掉,5号去掉影响都不大,这张图的割点是2号点,这下应该对割点有了解了吧

 好,了解了割点,开始解题

首先得解决一个最重要的问题,怎么判断割点?

我们先回顾一遍之前的题目:受欢迎的牛

我们这里判断割点的方法,就是比较当前点的时间戳和追溯值

这里分成两类讨论:

第一类:当前点不是根节点

这种情况下,我们遍历当前点能访问到的所有点 ,如果没找过,那么如果与当前点x相连的y点的追溯值大于等于了x点的时间戳,x就是割点

if(x!=root&&low[y]>=dfn[x])cut[x]=1;

为什么这么判断呢?我们画个图

 

第二类:当前点是根节点

是根节点就很简单了,只要他直接连接的点大于等于两个点,这个点一没,整张图是不是就断开了

所以,我们记录一下根节点连接的边的数量,如果大于等于二,它就是割点

分析完思路,代码放在下面

#include<bits/stdc++.h>
using namespace std;
const int N=1e6+5;
int n,m;
int dfn[N];//时间戳
int vis[N];
int low[N];//追溯值
int cut[N];
vector<int>a[N];
int cnt;
int times;
void tarjan(int x,int root){
	vis[x]=1;
	dfn[x]=low[x]=++times;
	int s=0;
	for(int i=0;i<a[x].size();i++){
		int y=a[x][i];
		if(dfn[y]==0){
			s++;//记录所能到达的边数量
			tarjan(y,root);
			low[x]=min(low[x],low[y]);
			if(x!=root&&low[y]>=dfn[x])cut[x]=1;//不是根
			if(x==root&&s>=2)cut[x]=1;//是根
		}
		else{
			low[x]=min(low[x],dfn[y]);
		}
	}
}
main(){
	scanf("%d%d",&n,&m);
	int u,v;
	for(int i=1;i<=m;i++){
		scanf("%d%d",&u,&v);
		a[u].push_back(v);
		a[v].push_back(u);
	}
	for(int i=1;i<=n;i++)if(dfn[i]==0)tarjan(i,i);
	int cnt=0;
	for(int i=1;i<=n;i++)if(cut[i])cnt++;
	printf("%d\n",cnt);
	for(int i=1;i<=n;i++)if(cut[i])printf("%d ",i);
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值