重生之FHQ-Treap

FHQ-Treap,启动!(按值合并、分裂)

注明

小说作者 BestMonkey,由于 pb_ds 写平衡树写挂

在这里插入图片描述
(乐辉!你个gd看看啊!)(<-题外话)索性学习手写平衡树,如你所见,学的是牛牛牛的 FHQ-Treap

参考文献:

建议先在 OI-Wiki 上粗略的学习一下 Treap

正文

为了方便讲解,选了题目 P3369 【模板】普通平衡树 为例题。

树的定义

定义 l _ s o n l\_{son} l_son 为左儿子编号, r _ s o n r\_son r_son 为右儿子编号。为了后续方便使用,加上了:

#define lson(x) Treap[x].l_son
#define rson(x) Treap[x].r_son

代码片段:

#define lson(x) Treap[x].l_son
#define rson(x) Treap[x].r_son
struct Porject_FHQ_Treap {
	int l_son, r_son;
	int val, key, sz;
} Treap[Size];

核心、基本操作

基本操作
建新的结点

注意键值随机。
跟着代码意会一下。

代码片段:

inline int New_Node(int val) {
	Treap[++Treap_Len].val = val,
	                  Treap[Treap_Len].key = rnd(),
	                                  Treap[Treap_Len].sz = 1;
	return Treap_Len;
}
信息上传合并

信息上传合并其实很简单。显然,此时,当前节点的大小就是左儿子和右儿子的大小加上 1 1 1
依然跟着代码意会一下。

代码片段:

inline void Push_Up(int now) {
	Treap[now].sz =
	    Treap[lson(now)].sz + Treap[rson(now)].sz + 1;
}
核心操作

核心操作其实就只有分裂、合并这两个,搞懂之后自然可以知道插入、删除、查询排名等操作怎么写。

分裂

分裂操作会将整棵树分裂为两棵树 x x x y y y,且 x x x 中的值全部小于等于给定的值, y y y 中的值全部大于给定的值。

代码片段:

inline void Split(int now, int val, int &x, int &y) { //将树按val分裂成两棵树,分别以x和y为根
	if (!now) {//分到底了
		x = y = 0;
		return;
	}
	if (Treap[now].val <= val) { //如果当前的值小于给定的,那么根据二叉搜索树的性质,给定的值一定在右子树中
		x = now; //确定其中一个根
		Split(rson(now), val, rson(now), y);//去右子树分裂
	} else {
		y = now;
		Split(lson(now), val, x, lson(now));
	}
	Push_Up(now);//儿子变了,更新
}
合并

合并操作会将两棵树 x x x y y y 合并为一棵,前提条件为 x x x 内的所有值都小于等于y内的所有值。
显然,合并后的树仍然满足Treap的性质。

代码片段:

inline int Merge(int x, int y) {//将x和y合并为一棵树,并将合并后的根返回
	if (!x || !y)
		return x ^ y; //就是求x和y中不为0的那一个
	if (Treap[x].key > Treap[y].key) { //这里默认大根堆
		rson(x) = Merge(rson(x), y);
		Push_Up(x);
		return x;
	} else {
		lson(y) = Merge(x, lson(y));
		Push_Up(y);
		return y;
	}
}

衍生的基本操作

其实学会上面的操作之后完全可以自己手写出插入、删除、查前驱等基本操作。

![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/b0d24123518141478365ce524aefd4c2.jpeg

插入

假如要插入的值为 v a l val val,则可以按照 v a l val val 将原树分裂为 x x x y y y
根据上文的定义, x x x 中的所有值一定都小于等于 y y y,因此可以直接将 x x x 和新结点合并,然后再重新与 y y y 合并即可。

代码片段:

inline void Insert(int val) {
	Split(Root, val, x, y);
	Root = Merge(Merge(x, New_Node(val)), y);
}
删除

假如要删除的值为 v a l val val,那么我们可以按照 v a l val val 将原树分裂为 x x x y y y,然后再按照 v a l − 1 val-1 val1 x x x 再分裂为 x x x z z z
根据上文的定义,此时 z z z 中的值一定全都与 v a l val val 相等。此时我们可以去掉 z z z 的根节点,最后再都重新合并回去即可。

代码片段:

inline void Erase(int val) {
	Split(Root, val, x, y),
	      Split(x, val - 1, x, z);
	z = Merge(lson(z), rson(z)),
	Root = Merge(Merge(x, z), y);
}
按值查找排名

设要查询的值为 v a l val val,那么我们可以按照 v a l − 1 val-1 val1 将原树分裂为 x x x y y y,然后此时左子树的大小就是比 v a l val val 小的值的个数,再加上 1 1 1 就是答案。
注意最后要合并回去。

代码片段:

inline int Order_Of_Key(int val) {
	Split(Root, val - 1, x, y);
	int res = Treap[x].sz + 1;
	Root = Merge(x, y);
	return res;
}
按排名查找值

按照 BST 的性质可知:

  • 若当前的左子树大小大于给定排名,答案就在左子树中,
  • 否则就去右子树。

注意去右子树的话,要将查询的排名减去左子树的大小,再包括自己占的一个。

代码片段:

inline int Find_By_Order(int rk) {
	int now = Root;
	while (now) {
		if (Treap[lson(now)].sz + 1 == rk)
			break;
		else if (Treap[lson(now)].sz >= rk)
			now = lson(now);
		else
			rk -= Treap[lson(now)].sz + 1, now = rson(now);
	}
	return Treap[now].val;
}
找前驱

设要查询的值为 v a l val val,那么我们可以将原树按 v a l − 1 val-1 val1 分裂为 x x x y y y,然后根据二叉搜索树的性质,我们在 x x x 中一直往右跳就可以找到前驱了。
在最后注意合并。

代码片段:

inline int Find_Pre(int val) {
	Split(Root, val - 1, x, y);
	int now = x, res;
	while (rson(now))
		now = rson(now);
	res = Treap[now].val,
	Root = Merge(x, y);
	return res;
}
找后继

设要查询的值为 v a l val val,那么我们可以将原树按照 v a l val val 分裂为 x x x y y y,然后根据BST的性质在y中一直往左跳就可以找到后继了。

代码片段:

inline int Find_Nxt(int val) {
	Split(Root, val, x, y);
	int now = y, res;
	while (lson(now))
		now = lson(now);
	res = Treap[now].val, Root = Merge(x, y);
	return res;
}

完整代码 (Damn) 按值分裂、合并

#include<bits/stdc++.h>
using namespace std;
mt19937 rnd(233);
#define lson(x) Treap[x].l_son
#define rson(x) Treap[x].r_son
const int Size = 1e5 + 5;
struct Porject_FHQ_Treap {
	int l_son, r_son;
	int val, key, sz;
} Treap[Size];
int Treap_Len, Root, x, y, z;
inline int New_Node(int val) {
	Treap[++Treap_Len].val = val,
	                  Treap[Treap_Len].key = rnd(),
	                                  Treap[Treap_Len].sz = 1;
	return Treap_Len;
}
inline void Push_Up(int now) {
	Treap[now].sz =
	    Treap[lson(now)].sz + Treap[rson(now)].sz + 1;
}
inline void Split(int now, int val, int &x, int &y) {
	if (!now) {
		x = y = 0;
		return;
	}
	if (Treap[now].val <= val) {
		x = now;
		Split(rson(now), val, rson(now), y);
	} else {
		y = now;
		Split(lson(now), val, x, lson(now));
	}
	Push_Up(now);
}
inline int Merge(int x, int y) {
	if (!x || !y)
		return x ^ y;
	if (Treap[x].key > Treap[y].key) {
		rson(x) = Merge(rson(x), y);
		Push_Up(x);
		return x;
	} else {
		lson(y) = Merge(x, lson(y));
		Push_Up(y);
		return y;
	}
}
inline void Insert(int val) {
	Split(Root, val, x, y);
	Root = Merge(Merge(x, New_Node(val)), y);
}
inline void Erase(int val) {
	Split(Root, val, x, y),
	      Split(x, val - 1, x, z);
	z = Merge(lson(z), rson(z)),
	Root = Merge(Merge(x, z), y);
}
inline int Order_Of_Key(int val) {
	Split(Root, val - 1, x, y);
	int res = Treap[x].sz + 1;
	Root = Merge(x, y);
	return res;
}
inline int Find_By_Order(int rk) {
	int now = Root;
	while (now) {
		if (Treap[lson(now)].sz + 1 == rk)
			break;
		else if (Treap[lson(now)].sz >= rk)
			now = lson(now);
		else
			rk -= Treap[lson(now)].sz + 1, now = rson(now);
	}
	return Treap[now].val;
}
inline int Find_Pre(int val) {
	Split(Root, val - 1, x, y);
	int now = x, res;
	while (rson(now))
		now = rson(now);
	res = Treap[now].val,
	Root = Merge(x, y);
	return res;
}
inline int Find_Nxt(int val) {
	Split(Root, val, x, y);
	int now = y, res;
	while (lson(now))
		now = lson(now);
	res = Treap[now].val, Root = Merge(x, y);
	return res;
}
signed main() {
	int n;
	cin >> n;
	int opt, x;
	while (n--) {
		cin >> opt >> x;
		if (opt == 1)Insert(x);
		else if (opt == 2)Erase(x);
		else if (opt == 3)cout << Order_Of_Key(x) << endl;
		else if (opt == 4)cout << Find_By_Order(x) << endl;
		else if (opt == 5)cout << Find_Pre(x) << endl;
		else cout << Find_Nxt(x) << endl;
	}
	return 0;
}

启动? 按大小分裂、合并

注明

我的意思是看解析的时候别玩原神

例题:文艺平衡树

先解惑:为什么代码里有 Push_Down (下传懒标记)的操作呢?(梦回线段树)

BlastMike:因为文艺平衡树这题比较特殊,不是简单的维护区间,有翻转,所以用懒标记标识是否要执行翻转操作。学过线段树总该明白吧。
在这里插入图片描述

核心操作

为什么只有核心操作的解读呢?因为其他操作都和之前一样(建议对前面的内容详细阅读)。

在这里插入图片描述

分裂

分裂操作其实就是把之前按值分裂的分裂操作中的比较值的大小改为比较大小(比较抽象。完了这下更抽象了。)。

注意不要写成:

if (Treap[now].sz < sz)

这相当于自己和自己比大小。(因为这个一个憨憨调了一个下午)

代码片段:

inline void Split(int now, int sz, int &x, int &y) {
	if (!now) {
		x = y = 0;
		return;
	}
	Push_Down(now);
	if (Treap[lson(now)].sz < sz) {
		x = now;
		Split(rson(now), sz - Treap[lson(now)].sz - 1, rson(now), y);
	} else {
		y = now;
		Split(lson(now), sz, x, lson(now));
	}
	Push_Up(now);
}

合并

想不到吧,和之前一样吗?

在这里插入图片描述

代码片段:

inline int Merge(int x, int y) {
	if (!x || !y)
		return (x == 0 ? y : x);
	if (Treap[x].key < Treap[y].key) {
		Push_Down(x),
		          rson(x) = Merge(rson(x), y),
		          Push_Up(x);
		return x;
	} else {
		Push_Down(y),
		          lson(y) = Merge(x, lson(y)),
		          Push_Up(y);
		return y;
	}
}

完整代码(按大小分裂、合并)

#include<bits/stdc++.h>
using namespace std;
#define lson(x) Treap[x].l_son
#define rson(x) Treap[x].r_son
mt19937 rnd(233);
const int Size = 100000 + 5;
struct Porject_FHQ_Treap {
	int l_son, r_son;
	int val, key, sz, ly_tag;
} Treap[Size];
int Treap_Len, Root;
inline int New_Node(int val) {
	Treap[++Treap_Len].val = val,
	                  Treap[Treap_Len].sz = 1,
	                                  Treap[Treap_Len].key = rnd();
	return Treap_Len;
}
inline void Push_Up(int now) {
	Treap[now].sz =
	    Treap[lson(now)].sz + Treap[rson(now)].sz + 1;
}
inline void Push_Down(int now) {
	if (Treap[now].ly_tag)
		swap(lson(now), rson(now)),
		     Treap[lson(now)].ly_tag ^= 1,
		                                Treap[rson(now)].ly_tag ^= 1,
		                                        Treap[now].ly_tag = 0;
}
inline void Split(int now, int sz, int &x, int &y) {
	if (!now) {
		x = y = 0;
		return;
	}
	Push_Down(now);
	if (Treap[lson(now)].sz < sz) {
		x = now;
		Split(rson(now), sz - Treap[lson(now)].sz - 1, rson(now), y);
	} else {
		y = now;
		Split(lson(now), sz, x, lson(now));
	}
	Push_Up(now);
}
inline int Merge(int x, int y) {
	if (!x || !y)
		return (x == 0 ? y : x);
	if (Treap[x].key < Treap[y].key) {
		Push_Down(x),
		          rson(x) = Merge(rson(x), y),
		          Push_Up(x);
		return x;
	} else {
		Push_Down(y),
		          lson(y) = Merge(x, lson(y)),
		          Push_Up(y);
		return y;
	}
}
inline void Reverse(int l, int r) {
	int x, y, z;
	Split(Root, l - 1, x, y),
	      Split(y, r - l + 1, y, z);
	Treap[y].ly_tag ^= 1;
	Root = Merge(Merge(x, y), z);
}
inline void Print(int now) {
	if (!now)
		return;
	Push_Down(now);
	Print(lson(now)),
	      cout << Treap[now].val << " ",
	      Print(rson(now));
}
signed main() {
	int n, m;
	ios::sync_with_stdio(false), cin.tie(0), cout.tie(0), cin >> n >> m;
	for (register int i = 1; i <= n; ++i)
		Root = Merge(Root, New_Node(i));
	int l, r;
	while (m--)
		cin >> l >> r,
		    Reverse(l, r);
	Print(Root);
	return 0;
}

戛然而止。

片尾

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/90145609344b49038cb030004c49ba56.png

  • 12
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 9
    评论
可持久化splay是一种数据结构,它是对splay树进行修改和查询的一种扩展。在传统的splay树中,对树的修改操作会破坏原有的树结构,而可持久化splay树则允许我们对树进行修改、查询,并且可以保存修改后的每个版本的树结构。 在可持久化splay树中,我们不会直接对原树进行修改,而是通过复制每个节点来创建新的版本。这样,每个版本都可以独立地修改和查询,保留了原有版本的结构和状态。每个节点保存了其左子树和右子树的引用,使得可以在不破坏原有版本的情况下进行修改和查询。 为了实现可持久化splay树,我们可以使用一些技巧,比如引用中提到的哨兵节点和假的父节点和孩子节点。这些技巧可以帮助我们处理根节点的旋转和其他操作。 此外,可持久化splay树还可以与其他数据结构相结合,比如引用中提到的可持久化线段树。这种结合可以帮助我们解决更复杂的问题,比如区间修改和区间查询等。 对于可持久化splay树的学习过程,可以按照以下步骤进行: 1. 理解splay树的基本原理和操作,包括旋转、插入、删除和查找等。 2. 学习如何构建可持久化splay树,包括复制节点、更新版本和保存历史版本等。 3. 掌握可持久化splay树的常见应用场景,比如区间修改和区间查询等。 4. 深入了解与可持久化splay树相关的其他数据结构和算法,比如可持久化线段树等。 在解决问题时,可以使用二分法来确定答案,一般称为二分答案。通过对答案进行二分,然后对每个答案进行检查,以确定最终的结果。这种方法可以应用于很多问题,比如引用中提到的在线询问问题。 综上所述,可持久化splay是一种对splay树进行修改和查询的扩展,可以通过复制节点来创建新的版本,并且可以与其他数据结构相结合解决更复杂的问题。学习过程中可以按照一定的步骤进行,并且可以使用二分法来解决一些特定的问题。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [[学习笔记]FHQ-Treap及其可持久化](https://blog.csdn.net/weixin_34283445/article/details/93207491)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *2* *3* [可持久化数据结构学习笔记](https://blog.csdn.net/weixin_30376083/article/details/99902410)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值