【No.12】蓝桥杯可撤销并查集|查找|合并|撤销(C++)

news2024/9/23 11:21:18

前置知识
蓝桥杯并查集|路径压缩|合并优化|按秩合并|合根植物(C++)-CSDN博客

可撤销并查集

关键注意

  1. 可撤销并查集的撤销功能如何实现
  2. 可撤销并查集能不能用路径压缩
    可撤销并查集(Reversible Union-Find)是一种扩展了标准并查集(Union-Find)数据结构的数据结构,它允许在进行连接(union)和查询(Find)操作的同时,能够回退(Undo)或者撤销之前的操作。这种数据结构通常用于支持一些需要回滚操作的应用,比如在离线算法中或者需要进行回退的决策问题中。当然主要是应用于在线算法,因为离线算法反悔可以完全输入后在一起处理。
举例

在蓝桥王国中有n个城市,王国经常会发生地震,会导致道路塌方,好在王国科技水平比较高也就只有刚修建好的道路会塌方,王国的人们出行前会询问两个地点是否连通。所以,你需要维护一棵动态森林,初始时包含n个散点,每个点代表一个独立的集合。然后,有m个操作,支持以下三种操作:连接操作(类型1):执行操作“1 u v”,将点u和点v连接起来,形成一个道路。这表示两个点之间有一条路。
查询操作(类型2):执行操作“2 u v”,用来査询点u和点v之间是否连通。如果它们在联通,输出"Yes",否则输出“NO"
撤销操作(类型3):执行操作"3",用于撤销上一个操作。这个操作需要保证存在被撤销的操作,即必须有之前的连接操作被撤销。

分析

我们采用并查集来表示联通,类似于最小生成树Kruskal算法,这样我们很容易就能完成op1和op2这两个操作。
但是第三个操作,如何去处理呢,怎么撤回呢,我们先不考虑路径压缩和启发式合并的情况下。并查集的合并为:
Fa[Find(i)]=j,我们只需要用变量记录t=Find(i)m=Fa[Find(i)]即可。当反悔的时候我们直接Fa[t]=m即可。
图片描述
但是既没有做路径压缩,又没有做启发式合并,那么查询效率是非常低的,所以我们很难通过一些复杂的题目。
所以我们还是需要去再去考虑优化办法。

路径压缩

我们考虑可不可以使用路径压缩来优化并查集。核心在于能够解决撤销上一步的能力。
我们考虑如果使用路径压缩优化后还能够撤销吗?
我们考虑这样一个图:
这样进行撤销的时候我们很容易记得上次操作的是什么,Fa[Find(i)]=j,我们只需要用变量记录t=Find(i)m=Fa[Find(i)]即可。当反悔的时候我们直接Fa[t]=m即可。按照我们刚才考虑的就行,那如果是路径压缩之后对于路径压缩之后的右图还能否恢复呢?
图片描述
对于某一个集合一次路径压缩之后就变成了右图所示,不能找到是那两个集合合并了。
即使我们假设上次合并的是90和1,t=fa[90]=4 m=fa[4]=4,然后进行恢复,fa[4]=4然而并不能恢复,即使在恢复90,fa[90]=4之后,只是90和4被从树中摘除,然而我们看原图。
路径压缩是不行的

启发式合并

90和4之间还有个8我们还要记录8,如果中间还有很多元素,在每次查询和合并时都被压缩,我们就更难处理了,实际上经过路径压缩后的局面比我们想象的更加复杂。
所以我们不能使用路径压缩,那我们可以进行合并吗?我们知道普通合并是可以使用的,那么启发式合并或者按秩合并可以吗?
当然可以,这两种合并方式对于并查集来说是没有用影响的,无非就是a树挂在b节点上或者b树挂在节点a上面,对于并查集整体的恢复操作不会产生什么影响。

算法原理

并查集是一种树形的数据结构,它主要用于处理一些不相交集合的合并及查询问题。可撤销并查集的基本原理与并查集相似,但增加了一个撤销操作,我们需要做的就是对这一操作进行维护。
为了提高效率,我们采用启发式合并的优化方法。在可撤销并查集中,我们不使用路径压缩因为这样会破坏树形结构,导致在撤销操作时无法准确找到原始的父节点。在合并操作中,由于不使用路径压缩,查询的复杂度为 O ( d ) O(d) O(d),其中d为树的深度。为了降低树的深度,我们采用了启发式合并(或按秩合并)的方法。

那么我们可以给可撤销并查集的算法原理:

  1. 查找
    由于支持撤销操作,那么肯定不能路径压缩,否则会破坏树形结构,反悔时无法找到原本的父节点。
  2. 合并
    既然不能路径压缩,那么查询的复杂度就是0(a)的(d为深度),所以我们要尽可能地减少树的深度,于是用到了一种合并方法–启发式合并(或按秩合并)启发式合并:
    维护每个集合的大小,合并时将小的集合合并到大的集合。
    时间复杂度:合并0(1),查找0(logn)。
  3. 撤销
    用栈来记录每次合并的操作,然后进行对fa,size等变量的维护即可
代码实现
1.定义所需变量
int n, q;
int fa[20000005], sz[2000005];

struct UndoObject
{
	int pos, val;
	UndoObject(int p, int v)
	{
		pos = p;
		val = v;
	}
};

stack<UndoObject> undo_sz, undo_fa;

用一个stack,先入后出,每次的操作往里放,先进去的会被晚撤销,撤销的一定是最近的这一步
undo_sz,记录树高
undo_fa,记录它的父亲是谁

声明了一些全局变量和数据结构,用于维护并查集的状态和实现撤销操作:
·n,q;:定义了两个全局变量n和q,分别表示数量和操作次数。fa[20000005],siz[20000005];:定义了两个数组,fa 用于表示所属的集合的父节点,sz 用于表示每个集合的大小。
Undo0bject:定义了一个结构体(类) Undo0bject,用于存储撤销操作的详细信息。每个Undo0bject对象包含两个成员变量pos和val,分别表示位置和值。
undo_sz,undo fa;:定义了两个Undo0bject堆栈,undo sz 用于记录每次连接操作的集合大小和父节点信息,undo fa用于记录每次连接操作的父节点信息。
这些全局变量和数据结构主要用于在并查集的基础上实现了撤销操作。通过堆栈undo_sz和undo fa 记录每次连接操作的详细信息,程序可以在需要时撤销最近的操作,恢复到之前的状态。

2.初始化
void init(int n)
{
	for (int i = 1; i <= n; i ++)
		fa[i] = i, sz[i] = 1;  //一棵点,就是自己
	while (!undo_sz.empty())
		undo_sz.pop();  //清除之前的栈的内容
	while (!undo_fa.empty())
		undo_fa.pop();  //清除之前的栈的内容
}

static void init (int n)
{
	fa = new int[n + 1];  //要避过0
	sz = new int[n + 1];
	for (int i = 1; i <= n; i ++)
	{
		fa[i] = i;
		sz[i] = 1;
	}
	undo_sz = new Stack<>();
	undo_fa = new Stack<>();
}

初始化函数 init,用于在开始一个新的测试用例时,清空之前的状态信息,确保并査集处于初始状态。
该函数执行了以下操作:

  • 初始化 fa 和 siz 数组:使用循环将每个初始化为独立的集合,即每个的父节点是自己,每个集合的大小为 1。
  • 清空 undosz和 undo fa 栈:使用 while 循环,对两个堆栈undo sz 和 undo fa 分别调用 pop 操作,将之前的栈内容清空。
    当开始一个新的测试用例时,调用 init函数可以确保之前的状态信息不会对当前测试用例产生干扰。
    这对于在一个程序中多次执行并查集操作,每次都需要一个干净的状态时是非常有用的。
3.查询代码
int find (int x)
{
	if (x == fa[x])
		return x;
	return find(fa[x]);
}

并查集中的查找函数。并查集是一种数据结构,它能够高效地进行合并和查找操作。这个查找函数的作用是找到给定元素所在的集合的根节点。
int find(int x):定义一个函数find,它接受一个整数参数x,表示要查找的元素
if(x==fa[x])return x;: 如果x的父节点就是自己,说明x就是它所在的集合的根节点,直接返回x。
return find(fa[x]);:否则,递归地査找x的父节点的根节点,即调用find函数。将fa[x]作为参数传递给find函数,因为fa[x]是x的父节点,它所在的集合的根节点就是x所在的集合的根节点的父节点

4.合并代码
static void merge(int u, int v)
{
	int x = find(u);
	int y = find(v);
	if(x == y)  //如果相同,就不合并了
		return;
	if(sz[x] < sz[y])
	{
		int temp =x;
		x = y;
		y= temp;
	}
	//把原来的值放进去
	undo_sz.push(new UndoObject(x,sz[x]));
	//存完以后,改掉
	sz[x] += sz[y];
	//把原来的值放进去
	undo_fa.push(new UndoObject(y, fa[y]));
	//存完以后,改掉
	fa[y]= x;
}

这是并查集中的合并(连接)操作的实现,用于将两个节点所属的集合合并成一个集合。函数merge的具体步骤如下:

  1. 使用find函数找到节点u所属集合的根节点x和节点v所属集合的根节点 y。
    1. 如果x和y相等,说明节点u和节点v已经在同一个集合中,无需合并,直接返回。
    2. 如果 sz[x]< sz[y],即集合 x的大小小于集合 y的大小,那么交换x和 y,确保较大的集合成为合并后的集合的根节点。
  2. 记录合并前的状态信息,将x的大小siz[x]和y的父节点 fa[y]分别入栈 undo_sz 和undo fa.
  3. 更新集合大小和父节点信息,将集合y合并到集合x中。
    这个合并操作不仅将两个集合合并,还通过栈undo_sz和 undo_fa 记录了合并前的状态信息,以便后续可能的撤销操作。这种记录状态的设计可以在撤销时还原到之前的状态,使得操作更加灵活。
5.撤销代码
void undo()
{
	fa[undo_fa.top().pos]=undo_fa.top().val;
	undo_fa.pop();
	siz[undo_sz.top().pos]=undo_sz.top().val;
	undo_sz.pop();
}

这是用于撤销最近一次连接操作的函数 undo的实现。该函数通过栈undo_ fa和undo_sz 中记录的信息,将最近一次连接操作的状态还原到连接之前的状态。具体步如下:

  1. 从 undo_fa 栈中取出最近一次连接操作前的状态信息,包括位置 pos 和父节点的值 val。然后,将城市 pos 的父节点更新为val。
  2. 从 undo_sz 栈中取出最近一次连接操作前的状态信息,包括集合的根节点位置pos 和集合的大小 val。然后,将集合 pos 的大小更新为 val。
  3. 分别从 undo_fa 和 undo_sz 栈中弹出这些信息;确保下一次 undo 操作不会再次撤销同一次连接操作。
    这个函数实现了撤销操作,可以在需要的时候将并查集的状态回滚到之前的状态

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1534636.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

你要的个性化生信分析服务今天正式开启啦!定制你的专属解决方案!全程1v1答疑!

之前在 干货满满 | 给生信小白的入门小建议 | 掏心掏肺版 中有提到&#xff0c;如果小伙伴们真的想学好生信&#xff0c;那编程能力是必须要有的&#xff01;但是可能有些小伙伴们并没有那么多的时间从头开始学习编程&#xff0c;又或是希望有人指导或者协助完成生信分析工作&a…

数据库原理及应用期末+考研复试

文章目录 一、数据库系统概述二、数据模型2.1E-R数据模型2.2层次数据模型2.3网状数据模型2.4关系数据模型 三、数据库系统的体系结构3.1数据库系统体系结构3.2数据库系统3.3数据库管理系统 四、关系数据库结构化查询语言——SQL语言4.1基本表定义4.2查询结果显示4.3查询满足条件…

【JDBC编程】 Java程序操作数据库

目录 一、数据库编程的必备条件 二、什么是JDBC&#xff1f; 三、JDBC的使用 1. 准备工作 2. 建立数据库连接 2.1 加载驱动程序 2.2 数据库连接池技术 3. 正式操作 四、JDBC的局限性与MyBatis的优势 一、数据库编程的必备条件 编程语言&#xff0c;如Java&#xff0…

揭秘爆红AI图像增强神器:Magnific AI如何做到1亿像素放大?

最近有个很火的AI图像增强应用&#xff0c;叫Magnific AI。 你知道吗&#xff0c;它发布一个多月就有40万人注册了&#xff01; 这个应用确实非常实用&#xff0c;它不仅利用AI技术放大了图像&#xff0c;还能提升分辨率&#xff0c;从而使图片呈现得更加清晰。 值得一提的是…

观后感-华为中国合作伙伴大会2024

非常荣幸能够参加华为中国合作伙伴大会 2024。以往我参加的会议多数是由政府单独举办的&#xff0c;然而这次进入会场后&#xff0c;我第一感觉就是无比震撼。我从未想过一家企业竟能举办如此规模宏大的会议&#xff0c;当我签到的时候才发现&#xff0c;自己已经是两万多名参会…

环境变量配置

举一个小例子来演示一下环境变量配置。 在CMD中打开QQ界面&#xff0c;首先需要知道QQ.exe文件的完整路径。一旦有了这个路径&#xff0c;可以按照以下步骤操作&#xff1a; 打开CMD窗口。可以通过按下Windows键R&#xff0c;输入“cmd”并回车来打开它。在CMD窗口中&#xf…

2024年语言艺术、人文发展与教育国际会议(ICLAHDE2024)

2024年文学、历史与艺术设计国际会议(ICLHAD2024) 一、【会议简介】 2024年国际语言艺术、人文发展与教育会议&#xff08;ICLAHDE2024&#xff09;将在中国昆明举行&#xff0c;主题为“语言、人文与艺术”。ICLAHDE汇集了来自世界各地语言艺术、人类发展和教育领域的学者、工…

图书推荐|图解算法:C语言实现+视频教学版

零负担理解数据结构及其算法的设计&#xff0c;零基础也能快速上手编程。 本书内容 《图解算法&#xff1a;C语言实现视频教学版》是一本综合讲述数据结构及其算法的入门书&#xff0c;力求简洁、清晰、严谨、且易于学习和掌握。 《图解算法&#xff1a;C语言实现视频教学版》…

【系统架构师】-第6章-数据库设计基础知识

1、三级模式-两级映像 外模式&#xff1a;视图、用户与数据库的接口 概念模式&#xff1a;表 内模式&#xff1a;存储方式&#xff0c;索引创建等 1&#xff09;外模式-模式映射&#xff1a; 视图与表的映射&#xff0c;表数据发生修改&#xff0c;只需要修改映射&#xf…

淘宝店铺如何从1688一键铺货?官方授权API接口,可满足多样化上货需求

那么新手卖家如何将1688的源头厂货一键铺货到淘宝店铺呢&#xff1f;下面我教大家几招&#xff1a; 1、通过淘宝复制一键复制上货 淘宝API接口采集 taobao.item_get 公共参数 名称类型必须描述keyString是调用key&#xff08;必须以GET方式拼接在URL中&#xff09;secretStr…

销售数据分析怎么做?用好这5个数据分析方法与模型就足够了。

企业经营其实简单来说就是做买卖&#xff0c;有了买卖自然就产生了销售数据&#xff0c;那怎么能让这些销售数据产生价值呢&#xff1f;答案就是数据分析。通过对销售数据的分析&#xff0c;可以帮助企业及时洞察市场动向&#xff0c;发现企业销售过程中的问题&#xff0c;调整…

用户行为分析是什么?为什么我们需要 bitmap?

本文非常好&#xff1a;https://blog.bcmeng.com/post/doris-bitmap.html meta搜也非常好&#xff1a;https://metaso.cn/ 用户行为分析是什么&#xff1f;简单说&#xff0c;就是围绕全体用户&#xff0c;做各种分析。用户就是一个个的 id。id 在不同方面有各种行为记录&…

c语言--字符转换函数(tolower、toupper.)

目录 一、前言二、使用举例 一、前言 C语⾔提供了2个字符转换函数&#xff1a; int tolower ( int c ); //将参数传进去的⼤写字⺟转⼩写 int toupper ( int c ); //将参数传进去的⼩写字⺟转⼤写二、使用举例 #include <ctype.h> #include<stdio.h> int main(…

母亲的奶牛(蓝桥杯,acwing每日一题)

题目描述&#xff1a; 农夫约翰有三个容量分别为 A,B,C升的挤奶桶。 最开始桶 A 和桶 B 都是空的&#xff0c;而桶 C 里装满了牛奶。 有时&#xff0c;约翰会将牛奶从一个桶倒到另一个桶中&#xff0c;直到被倒入牛奶的桶满了或者倒出牛奶的桶空了为止。 这一过程中间不能有…

LeetCode刷题记录:(12)全排列2

leetcode传送通道 class Solution {List<List<Integer>> result new ArrayList<>();List<Integer> path new ArrayList<>();boolean[] used;public List<List<Integer>> permuteUnique(int[] nums) {used new boolean[nums.lengt…

Java代码基础算法练习-求一个三位数的各位平方之和-2024.03.21

任务描述&#xff1a; 输入一个正整数n&#xff08;取值范围&#xff1a;100<n<1000&#xff09;&#xff0c;然后输出每位数字的平方和。 任务要求&#xff1a; 代码示例&#xff1a; package march0317_0331;import java.util.Scanner;public class m240321 {public …

OpenCV4.9.0开源计算机视觉库安装教程

返回&#xff1a;OpenCV系列文章目录&#xff08;持续更新中......&#xff09; 引言&#xff1a;OpenCV系列文章中的安装部分今天全部完成了&#xff0c;为了读者更方便阅读&#xff0c;大家可以按下列索引前往&#xff0c;成文较为仓促有错漏在所难免&#xff0c;欢迎大家指正…

CircuitBreaker熔断器

CircuitBreaker熔断器 1、Hystrix目前也进入维护模式 ​ Hystrix是一个用于处理分布式系统的延迟和容错的开源库&#xff0c;在分布式系统里&#xff0c;许多依赖不可避免的会调用失败&#xff0c;比如超时、异常等&#xff0c;Hystrix能够保证在一个依赖出问题的情况下&…

Uibot6.0 (RPA财务机器人师资培训第2天 )采购付款——网银付款机器人案例实战

训练网站&#xff1a;泓江科技 (lessonplan.cn)https://laiye.lessonplan.cn/list/ec0f5080-e1de-11ee-a1d8-3f479df4d981https://laiye.lessonplan.cn/list/ec0f5080-e1de-11ee-a1d8-3f479df4d981(本博客中会有部分课程ppt截屏,如有侵权请及请及时与小北我取得联系~&#xff0…

关于IP地址的查询方法

IP全称Internet Protocol&#xff0c;即互联网协议&#xff0c;是一种网络通信协议&#xff0c;主要用于实现网络互连和设备之间的通信。它规定了计算机在因特网上进行通信时应当遵守的规则&#xff0c;使得不同设备之间能够互相识别、连接和交换信息。IP地址则是IP协议提供的一…