连通分量 —— # 割边 # 割点 的定义

割点

定义:什么是割点

在无向图中,所有能联通的点构成了一个“联通分量”。在一个联通分量中有一些关键点,如果删除它,会把这个联通分量分成两个或更多。这种点称为割点

寻找:判断割点的方法——DFS求割点的算法

首先是口胡原理:

在一个联通分量G中,对任意点a做DFS(能访问所有节点),构成一棵“深搜优先生成树T”,
定理一:T的根节点s 是割点当且仅当 s有两个或更多的子节点时成立
理解一:如果s是割点,它会把图分成不相连的几部分,这几个部分都会生成子树;如果s不是割点,它只会连接一个子树。
定理二:T的非根节点u是割点,`当且仅当u存在一个子节点v,v及其后代都没有回退边连回u的任意祖先。
理解二:如果u是割点,它会把图分为两部分或更多,其中至少一个后代肯定没有通过其他边(回退边,绕过u回去的边)回到u的某祖先,否则图不会被分开。
>多看看书吧……

然后是代码实现:

定义:
u的一个直接后代是v
num[u]:记录DFS对每个点的访问顺序
low[v]:记录v与v的后代能连回到的深度最低祖先的num
只要low[v]>=num[u]就说明在v这个支路上没有回退边连回u的祖先,最多回到u本身。根据定理二,可以知道u是一个割点。

代码:

#include<bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10, M = 2 * N;
int n, m, dfn[N], cnt, low[N];
int e[M], ne[M], h[N], idx;
bool iscut[N];
void dfs(int u, int root) {
	low[u] = dfn[u] = ++ cnt;
	int child = 0;
	for(int i = h[u]; ~i; i = ne[i]) {
		int v = e[i];
		if(!dfn[v]) {//没搜过,说明是树上的节点
//			if(u == root) 
			child ++;
			dfs(v, root);
			low[u] = min(low[u], low[v]);
			if(low[v] >= dfn[u] && u != root) 
				iscut[u] = 1;
		}	
		else //搜过,说明不是树上的节点,是回边。
			low[u] = min(low[u], dfn[v]);
	}
	if(u == root && child >= 2) iscut[root] = 1;
}

void add(int a, int b) {
	e[idx] = b, ne[idx] = h[a], h[a] = idx ++;
}

int main() {
	memset(h, -1, sizeof h);
	scanf("%d%d", &n, &m);
	for(int i = 1; i <= m; i ++) {
		int a, b; scanf("%d%d", &a, &b);
		add(a, b), add(b, a);
	}
	for(int i = 1; i <= n; i ++) 
		if(!dfn[i])
			dfs(i, i);
	int ans = 0;
	for(int i = 1; i <= n; i ++) 
		if(iscut[i])
			ans ++; 
	printf("%d\n", ans);
	for(int i = 1; i <= n; i ++) 
		if(iscut[i])
			printf("%d ", i);
	return 0;
}

割边

定义:

类似割点,在一个联通分量中,如果删除一个边,把这个联通分量分成了两个,这个边称为割边。

寻找:判断割点的方法——DFS求割点的算法

有意思的是:只要将low[v]>=num[u]改为low[v]>num[u],那么边(u,v)就是一条割边。
这表示u的支路v以及v的后代只能回退到v,到不了u,那么边(u,v)肯定是割边

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值