首页 > 其他分享 >学习笔记——图的联通性问题

学习笔记——图的联通性问题

时间:2023-04-13 15:34:58浏览次数:36  
标签:连通 联通 割点 笔记 学习 dfn low 节点 分量

割点与割边

定义

连通分量:在一张无向图中的极大连通子图即为该图的连通分量。

割点:去掉这个点后,这张无向图的连通分量数量增加,则这个点称为这个图的割点。

割边:去掉这条边后,这张无向图的连通分量数量增加,则这条边称为这个图的割边。

求割点

主要思路

以下提到的有关树的内容,全部指的是对连通分量做 DFS 得到的 DFS 生成树上的内容。

我们对一个连通分量做一次 DFS,可以得到一棵搜索树。这里引入两个定理:

  1. 如果这棵 DFS 生成树的根节点至少有两个儿子节点,那么这个根节点是割点。

  2. 这棵 DFS 生成树中的非根节点的一个儿子节点不能不通过它们之间的连边回到这个点的祖先,那么这个点就是割点。

先看定理 \(1\):

这个很好理解,如果这个根节点不是割点,说明根节点只有一个儿子,因为其他点都可以在不经过根节点的情况下两两到达。看图:

图中黑色边为以 \(1\) 为根的搜索树边,红色边为原图中的边。由于 \(1\) 只有一个儿子,所以 \(1\) 不为割点。

否则将这个节点删掉,一定会使连通分量的数量增加,因为有节点必须通过根节点才能到达别的节点。如图:

黑色边是以 \(3\) 为根的搜索树边,红色边为原图中的边。可以看到,根节点 \(3\) 拥有两个儿子,因为 \(1\) 与 \(4\),\(2\) 与 \(5\) 等都不能在不经过 \(3\) 的情况下互达,所以 \(3\) 为割点。

我们再看定理 \(2\):

如果删掉这个点,连通分量数量变多,说明有点不再与其他点连通,即没有返祖边连回这个点的祖先。否则一定可以通过这条返祖边连回这个点的祖先,那么连通分量数量也不会增加。

在这个图中,搜索树以 \(1\) 为根,可以发现,节点 \(4\) 无法不通过 \(4 - 5\) 这条边返回 \(5\) 的祖先,所以 \(5\) 是割点。如果加上 \(1 - 4\) 的一条边,再将 \(5\) 节点删去:

可以看到,连通分量数量并没有增加。因为节点 \(4\) 可以通过 \(1-4\) 的返祖边退回 \(5\) 的祖先 \(1\),所以并不会断开。

那么要如何实现呢?我们可以在 dfs 时记录一个点的 \(dfn\)(dfs 序),以及这个点的儿子节点中能回到的祖先的最小的 \(dfn\),记为 \(low\)。

那么如何更新 \(low\)?假如现在遍历到节点 \(u\),下一步要遍历节点 \(v\):

  1. 在遍历完 \(v\) 后回溯时,用 \(low_v\) 更新 \(low_u\)。

  2. 如果 \(u - v\) 是返祖边,用 \(dfn_v\) 更新 \(low_u\)。

那么判断割点也很好判断了,根据定理 \(2\),如果这时 \(low_v \ge dfn_u\),那么 \(u\) 是割点,因为 \(v\) 不能通返祖边回到 \(u\) 的祖先节点。对于定理 \(1\) 记录儿子个数特判即可。

举个例子:

对于上图,我们从 \(1\) 开始 dfs,整个算法过程如下:

遍历节点 \(1\),\(dfn_1\) 赋值为 \(1\),\(low_1\) 赋值为 \(1\);

遍历节点 \(2\),\(dfn_2\) 赋值为 \(2\),\(low_2\) 赋值为 \(2\),根节点 \(1\) 的儿子数更新为 \(1\);

遍历节点 \(5\),\(dfn_5\) 赋值为 \(3\),\(low_5\) 赋值为 \(3\);

遍历节点 \(4\),\(dfn_4\) 赋值为 \(4\),\(low_4\) 赋值为 \(4\);

遍历节点 \(3\),\(dfn_3\) 赋值为 \(5\),\(low_3\) 赋值为 \(5\);

发现节点 \(2\) 已经被遍历,所以 \(3 - 2\) 是一条返祖边,将 \(low_3\) 更新为 \(2\);

回溯至节点 \(4\),将 \(low_4\) 更新为 \(2\);

回溯至节点 \(5\),将 \(low_5\) 更新为 \(2\);

回溯至节点 \(2\);

回溯至节点 \(1\),由于 \(low_2 \ge dfn_1\),所以 \(2\) 是割点;

遍历节点 \(6\),\(dfn_6\) 赋值为 \(6\),\(low_6\) 赋值为 \(6\),根节点 \(1\) 的儿子数更新为 \(2\);

遍历节点 \(7\),\(dfn_7\) 赋值为 \(7\),\(low_7\) 赋值为 \(7\);

遍历节点 \(8\),\(dfn_8\) 赋值为 \(8\),\(low_8\) 赋值为 \(8\);

发现节点 \(6\) 已经被遍历,所以 \(8-6\) 是一条返祖边,将 \(low_8\) 更新为 \(6\);

回溯至节点 \(7\),将 \(low_7\) 更新为 \(6\);

回溯至节点 \(6\);

回溯至节点 \(1\),由于 \(low_6 \ge dfn_1\),所以 \(6\) 是割点;

由于根节点 \(1\) 的儿子数 \(\ge 2\),所以根节点 \(1\) 是割点。

所以这张图上的割点为 \(1,2,6\)。

code

#include<bits/stdc++.h>
using namespace std;
const int maxn=1e5+10;
int n,m,dfn[maxn],low[maxn],f[maxn],cnt;
vector<int> G[maxn];
void dfs(int cur,int u,int fa){
	dfn[u]=low[u]=++cnt;
	int sum=0;
	for(int i=0;i<G[u].size();i++){
		int v=G[u][i];
		if(!dfn[v]){
			sum++;
			dfs(cur,v,u);
			low[u]=min(low[u],low[v]);
			if(low[v]>=dfn[u]&&u!=cur){
				f[u]=1;
			}
		}
		else if(dfn[v]<dfn[u]&&v!=fa){
			low[u]=min(low[u],dfn[v]);
		}
	}
	if(u==cur&&sum>=2){
		f[cur]=1;
	}
}
int main(){
	ios::sync_with_stdio(false);
	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]){
			dfs(i,i,-1);
		}
	} 
	int ans=0;
	for(int i=1;i<=n;i++){
		if(f[i]) ans++;
	}
	cout<<ans<<endl;
	for(int i=1;i<=n;i++){
		if(f[i]) cout<<i<<" "; 
	} 
	return 0;
}

求割边

求割边,只要将求割点的条件从 \(low_v \ge dfn_u\) 改为 \(low_v > dfn_u\) 就可以了。

为什么?

考虑何时 \(low_v = dfn_u\)。

当从 \(v\) 出发不经过 \(u-v\) 能到达 \(dfn\) 最小的点为 \(u\) 时,\(low_v = dfn_u\)。也就意味着 \(v\) 不通过 \(u-v\) 最多只能返回父亲 \(u\)。如图:

图中节点 \(2\) 是一个割点。它的 \(dfn\) 为 \(2\),而它在 dfs 树上的儿子 \(5\) 的 \(low=2\),节点 \(5\) 不经过 \(5-2\) 最多只能返回 \(2\),所以 \(2\) 是割点。

但是我们注意到 \(1-2\) 这一条边:它是一条割边。图中 \(dfn_1=1\),而 \(low_2=2\),说明 \(2\) 不经过 \(1-2\) 最多只能到 \(2\),连父亲节点也回不去了,所以 \(1-2\) 自然就是割边。

双连通分量

定义

点双连通分量:如果一个连通分量中不存在割点,则这个连通分量称为点双连通分量。

边双连通分量:如果一个连通分量中不存在割边,则这个连通分量称为边双连通分量。

求点双连通分量

问题:在一张无向图 \(G\) 上,有多少个点双?

这里有一个性质:割点会把一个连通分量分为若干个点双。

所以我们在求解割点的时候,使用栈来记录已经遍历过的。当我们找到割点的时候,我们已经完成了对一个点双的遍历,所以此时栈中的元素就是一个点双。

为什么放入栈中的不是点,是

因为一条边只属于一个点双,而一个点可以属于多个点双,当这个点被弹出,就意味着这个点不能属于其他点双,所以存点会错。

标签:连通,联通,割点,笔记,学习,dfn,low,节点,分量
From: https://www.cnblogs.com/luqyou/p/17297063.html

相关文章

  • 深度学习-个人理解
    深度学习-个人理解深度学习模型类似一个黑盒子,输入一组数据,产生一个输出,这个输出就可以称为得分函数的输出值。根据输出值与实际值之间的比较,通过损失函数可以求得损失值。损失值越大,代表模型的分类效果越差。其中,通过Softmax分类器可以将分类结果映射成概率。前向传播和反向......
  • Vuex笔记
    Vuex有state,mutation,actions,getter四种用法如下:1、state(存储数据):state{count:0//全局数据}获取state数据两种方式:this.$store.state.全局数据名称利用辅助函数mapstateImport{mapState}from“vuex”computed:{…mapstate([“全局数据名称”])}2、mutation(更......
  • Cadence应用笔记:调整栅格
    说明栅格是在设计中非常常用的工具,修改栅格大小和显示可以通过如下操作:空白处右键选择设置Grid设置层叠各层的栅格大小或者全局大小通过该按键可以隐藏或者显示栅格......
  • 机器学习
    一、机器学习概述1、人工智能概述人工智能发展必备三要素:数据算法计算力人工智能、机器学习、深度学习机器学习是人工智能的一个实现途径深度学习是机器学习的一种方法机器学习、深度学习能做什么传统预测:店铺销量预测、量化投资、广告推荐、企业客户分类、SQL语句......
  • Pandas 学习手册中文第二版:1~5
    原文:Learningpandas协议:CCBY-NC-SA4.0译者:飞龙一、Pandas与数据分析欢迎来到《Pandas学习手册》!在本书中,我们将进行一次探索我们学习Pandas的旅程,这是一种用于Python编程语言的开源数据分析库。pandas库提供了使用Python构建的高性能且易于使用的数据结构和分......
  • Pandas 学习手册中文第二版:6~10
    原文:Learningpandas协议:CCBY-NC-SA4.0译者:飞龙六、索引数据索引是用于优化查询序列或数据帧中的值的工具。它们很像关系数据库中的键,但是功能更强大。它们为多组数据提供了对齐方式,还带有如何处理数据的各种任务(如重采样到不同频率)的语义。您将对Pandas执行的许多建......
  • Pandas 学习手册中文第二版:11~15
    原文:Learningpandas协议:CCBY-NC-SA4.0译者:飞龙十一、合并,连接和重塑数据数据通常被建模为一组实体,相关值的逻辑结构由名称(属性/变量)引用,并具有按行组织的多个样本或实例。实体往往代表现实世界中的事物,例如一个人,或者在物联网中,是一个传感器。然后,使用单个数据帧对每个......
  • CS231N assignment 2#3 _ dropout 学习笔记 & 解析
    dropout定义&作用&基本实现如课程所说,dropout最大的意义在于防止过拟合.我们还记得,dropout在网络架构上介于激活函数之后,下一层输入之前.想法很简单,就是将隐含层的某些数据屏蔽掉,直接从以输入到下一层,概率为p. 需要注意的是,dropout是仅针对训练而言的,测试......
  • 论结构化、系统性的学习
    在大的工作环境以及普遍的生活压力下。对以后充满了迷茫。尤其是30多岁以后的人生。中年的危机与焦虑如何避免?职场的规划与路线怎么制定?生活的压力与焦灼如何解决?家庭的压力.....其实主要还是职场的规划。人,一般来说,对于百分之九十九以上的人,都是要工作的。那么在国内这样......
  • Python程序笔记20230305
    n以内能被m整除的数的和、积最初版本计算指定数字内所有偶数的和n=int(input("请输入指定的n:"))i=0mysum=0whilei<=n: ifi%2==0: mysum=mysum+ii=i+1print(f"{n}以内的所有偶数的和是{mysum}")print("{0}以内的所有偶......