开拓计划4 - 二叉树与并查集

开拓计划4 - 二叉树与并查集

二叉树

二叉树的概念

  • Q:什么是树?
  • A:一种有 \(n\) 个节点最多有 \(n-1\) 条边的图。
  • Q:什么是二叉树?
  • A:每个节点都最多只有两个子节点的树。

二叉树和递归

  • Q:二叉树和递归有什么关系?

  • A:在执行递归的时候总是会自己调用自己,每一次调用都会产生新的一层,新的这几个需要执行的函数就成为了当前节点的子节点,一颗递归树就能画出来。

NKOJ 4376 遍历二叉树1

思路:二叉树的入门应用。

实现方法

  • 先序遍历是根左右,中序遍历是左根右,后序遍历是左右根。
  • 按照顺序,执行输出,向左递归,向右递归。

注意事项

  1. 在遍历二叉树时,注意是从根节点开始,不是从 \(1\) 号结点开始。

NKOJ 3909 遍历二叉树2

思路:二叉树的简单应用。

实现方法

  • 用先序确定根节点,再通过中序,确定哪些是左子树,那些是右子树。
  • 左右分别递归下去,要求后续,递归写完再输出。

注意事项

  1. 注意递归时不要把自己也递归了。

NKOJ 8599 无根树

思路:二叉树的简单应用。

实现方法

  • 简单地从根节点开始递归遍历二叉树,每深入一层记录层数的 k\(+1\) 每次取 k 的最大值。

注意事项

  1. 一定不要用邻接矩阵存储二叉树,会 TLE

并查集

对并查集的一些看法

  • 并查集虽然是和集合有关,但是并没有遵循数学中对集合的严格规定,表达的更多是一种节点与节点之间的关系。

并查集的原理

  • Q:并查集的使用场景和理论基础是什么?

  • A:在二叉树中,每个节点都有自己的父节点(可以认为根节点的父节点是它自己),如果题目给了节点之间的关系(不一定是从属关系),就可以把两个节点放在同一棵二叉树里,如果要找两个节点在不在同一个集合,只需要找它们的父亲节点是否相同。

  • Q:并查集如何存储?

  • A:由于并查集需要经常找父亲,所以我们在数组中存储每个节点的父节点编号,初始时每个节点的父节点是它自己。

  • Q:并查集如何查找?

  • A:不断地向上递归,只要当前节点有父就继续想上找父节点,直到找到根节点就返回。

  • Q:并查集如何合并?

  • A:有两个节点要合并,就先判断是否在同一个集合中,如果不在就将一个的根节点变成另一个的根节点。

并查集的路径压缩

  • Q:为什么要路径压缩?
  • A:当并查集的所有节点成一条链状的时候,向上查找的时间复杂度会达到 \(O_n\) 我们无法接受,采用路径压缩优化。
  • Q:路径压缩怎么写?
  • A:当我要向上找一个节点的父节点只要找到祖宗,就将它的父节点直接设为祖宗节点(有点像一个城市要向中央汇报工作,要先向省级政府汇报,但如果是直辖市就可以直接跳过省级政府)。

并查集模板代码

int fa[20005];
int find(int x){
	if(fa[x]==x) return x;
	return fa[x]=find(fa[x]);//路径压缩
}
//核心找父节点
void merge(int x,int y){
	int fx=find(x);
	int fy=find(y);
	if(fx!=fy) fa[fx]=fy;
}
bool same(int x,int y){
	return find(x)==find(y);
}
void init(){
	for(int i=1;i<=n;i++) fa[i]=i;
}

NKOJ 1205 亲戚

思路:并查集模板题

NKOJ 3197 岛屿

思路:并查集模板题

NKOJ 5614 加点

思路:可以直接到的加一个并查集,一共有几个并查集

NKOJ 2081 学外语

思路:会同一种语言的员工加一个并查集,一共有几个并查集

NKOJ 5681 交换排列

思路:一道看着像思维题的并查集

实现方法

  • 能交换的两个点之间连一条边,计算有多少个点和它应该在的位置不在同一个集合中。

带权并查集

带权并查集的原理

  • Q:带权并查集的原理是什么?
  • A:带权并查集,是在常规的并查集的基础上给每一个点都加上了权值,并在路径压缩和合并的时候更新。给每一个点都带上权值,大大提升了并查集的作用范围。
  • Q:权值可以带什么?
  • A:一般可以带区间和、区间和的奇偶性、点与点之间的关系 etc.

带权并查集的模板代码

以求区间和为例

void getf(int x){
	if(f[x]!=x){
		int t=f[x];
		f[x]=getf(f[x]);
		val[x]+=val[t];//更新权值
	}
	return f[x];
}
void merge(int x,int y,int v){
	int fx=getf(x),fy=getf(y);
	if(fx==fy) return;
	f[fx]=fy;
	val[fx]=val[y]-val[x]+v;
}

时光倒流思想

  • Q:什么是时光倒流思想?
  • A:时光倒流思想,是将所有的输入数据输入进来,发现用题目告诉我们的方法直接用数据结构模拟难以实现(如并查集难以实现删边),将数据倒序操作变成原来的相反操作(如并查集的删边变成加边)。

NKOJ 5682 果老师炸桥

题意

  • 对于一个无向图,每次删一条边,求每次删完后有多少对点无法互相到达。

思路:带权并查集+时光倒流

实现方法

  • 每次合并时将集合大小一同进行合并,每删一次边就会有那两个集合内的点两两不可达。
  • 由于可撤销并查集,代码多时间高,于是采用时光倒流和简单带权并查集。

NKOJ 2281 方块游戏

思路:带权并查集

实现方法

  • 用两个数组分别记录当前方块堆的高度,和距离根节点的距离,只有这样才能使用路径压缩。
  • 每次在 getf 函数中更新距离根节点的距离,在 merge 函数中更新 dishgt 。把距离根节点的距离加上堆在它顶上那一堆原来的高度,高度也加上那一堆的高度,把被移动后的高度归零。
  • 每次查询的时候要先跑一遍 getf ,类似于线段树的下放懒标记的操作。
posted @ 2024-12-14 09:59  hsr_ray  阅读(40)  评论(0)    收藏  举报