FHQ treap(无旋treap)
平衡树
- 平衡树作为一种中级数据结构,有着广泛的使用场景。其平衡性的维护方式灵活多变,而其中的无旋treap更以简单著称
P3369 【模板】普通平衡树
- 题意:
需维护以下操作:
插入一个数 x。
删除一个数 x(若有多个相同的数,应只删除一个)。
定义排名为比当前数小的数的个数 +1。查询 x 的排名。
查询数据结构中排名为 x 的数。
求 x 的前驱(前驱定义为小于 x,且最大的数)。
求 x 的后继(后继定义为大于 x,且最小的数)。 - 平衡树是一种二叉搜索树,而treap将其与堆结合,使其同时具有二者的性质。
treap通过给每个节点一个随机数值,依靠这个值维护一个堆,保证树的平衡性。
而无旋treap通过两种关键操作来维护这两者的性质:split和merge
split
- split将目标树按值分裂成两棵树,由于分裂后需要返回两颗树的根,不好返回,因此用指针&x和&y来传递分裂后的两个根
由于二叉搜索树的性质:u左子树的节点值都比u的权值小,而右子树的都比u的大。因此如果分裂目标k比u小,那么就去左子树找
反之亦然
由于二叉搜索树的性质,分裂出来的x子树中的所有值都比y子树中的所有值小。这个性质在合并时非常关键
代码:
#define ls (tr[u].l)
#define rs (tr[u].r)
void push_up(int u)
{
tr[u].size=1+tr[ls].siz+tr[rs].siz;
}
void split(int u,int &x,int &y,int k)
{
if(u==0){x=0,y=0;return;}
if(tr[u].val<=k)
{
x=u,split(rs,rs,y,k);
}
else y=u,split(ls,x,ls,k);
push_up(u);
}
容易理解,split并不破坏原treap堆的性质
- 注意 代码中关于&x与&y的确定需要特别强调。x代表分裂出来权值较小的子树的根节点,y代表分裂出来权值较大的子树的根节点,如下图

- 如果k比u的值大,证明整个左子树都比k小,因此这时的k就在k1的位置上。
因此u及其子树k1左边的全是属于其父亲给出的x,即分裂出的较小的子树。而较大的子树还不知道,就到k1右边的橙色部分中找
而将u的右子树带到split函数中x的位置,是为了更新u的右子树,在分裂完成后将剩下的小于k的部分接回u右子树的空缺
这个过程中,左子树无影响。分裂完后,u的大小很可能改变,因此要push_up更新大小
而k在图中左边的原理同理,不再赘述。
merge
- merge是将两个二叉平衡树合并的函数,要求以x为根的子树中所有元素都必须小于y子树中的元素。这个要求看似苛刻,但正好与split出来的子树的性质不谋而合。
- 由于合并的两棵树x中所有值都比y小,因此对于二叉搜索树的性质,只需要将大的树接在小的树右下角或者小的接在大的左下角就行了。
- merge的关键是一个随机值:\(tr[u].rnd\) 。还记得前文说到treap是二叉搜索树与堆的结合吗?split并不破坏堆的性质,而merge正好就根据初始所附的随机值来维护堆的性质。如何维护具体看代码
int merge(int x,int y) //返回的是两树合并后的根节点
{
if(!x||!y) return x+y; //如果x或y中任意一一个为零,就直接以另一个为根
if(tr[x].rnd<tr[y].rnd) //维护的是小根堆。若x的随机值比y的小,那么x就在y上面,x作为根被返回
{
tr[x].r=merge(tr[x].r,y);push_up(x);return x;
}
else
{
tr[y].l=merge(x,tr[y].l);push_up(y);return y;//否则y当根,注意merge中带入的第一棵树值都比第二颗小
}
}
具体操作
- 讲了这么多,实际上与开头具体的操作关系并不大。现在,就看看如何用以上两个函数实现这些操作
- 插入
先新建一个节点,再按这个点的值进行分裂。这时,其中一个子树中所有值都小于等于新建节点的值。依次合并回去就好了
mt19937 myrand(time(0));
int newnode(int val)
{
int u=++cnt;
tr[u].siz=1,tr[u].val=val,tr[u].l=tr[u].r=0,tr[u].rnd=myrand();
return u;
}
int u=newnode(x);//x是输入进来的权值
int lrt,rrt;
split(root,lrt,rrt,x);
root=merge(merge(lrt,u),rrt);//注意合并时都要给root重新赋值
- 删除
按x-1、x、x+1分裂成3个树,中间那个树所有权值都是x了。但注意不能将整个树全部扔掉。直接将中间树根节点的左右儿子合并即可。
int lr,mr,rr;
split(root,lr,rr,x-1);split(rr,mr,rr,x+1);
mr=merge(tr[mr].l,tr[mr].r);
root=merge(merge(lr,mr),rr);
- 查找x的排名
按x-1分裂,直接输出较小的树的siz即可
int lr,rr;
split(root,lr,rr,x-1);
printf("%d\n",tr[lr].siz+1);
root=merge(lr,rr);
- 查找排名为x的数
根据二叉搜索树的性质直接找
int kth(int u,int k)
{
int lsiz=tr[ls].size;
if(lsiz+1==k) return u;
if(lsiz+1>k) return kth(ls,k);
else return kth(rs,k-lsiz-1);
}
printf("%d\n",tr[kth(root,x)].val);
- 求x的前驱
仍然利用kth函数,将root按x-1分裂成两个树,再找较小子树中最大的数
int lr,rr;
split(root,lr,rr,x-1);
printf("%d\n",tr[kth(lr,tr[lr].siz)].val);
root=merge(lr,rr);
- 求x的后继
几乎同上,按x+1分裂,找较大子树中最小的数
int lr,rr;
split(root,lr,rr,x+1);
printf("%d\n",tr[kth(rr,1)].val);
root=merge(lr,rr);
- 以上,就是普通平衡树的全部操作。
P2042 [NOI2005] 维护数列
- 题意:
需要对一个连续数列维护以下操作:
区间插入
区间删除
区间修改
区间翻转
区间求和
求当前数列最大字段和 - 普通平衡树是维护一个集合的信息,那平衡树能不能维护一个连续数列呢?答案是可以的
- 普通平衡树与数列平衡树最大的区别在于split。
- 由于普通平衡树维护的是集合,操作大都与值相关,因此是按值分裂的
而数列平衡树维护的是连续的数列,因此不能按值分裂,必须按位置分裂
FHQ treap(无旋treap)的更多相关文章
- fhq Treap(无旋Treap)
先吹一波fhq dalao,竟然和我一个姓,我真是给他丢脸. 昨天treap就搞了一下午,感觉自己弱爆了.然后今天上午又看了一个上午的无旋treap再次懵逼,我太弱了,orzorz. 所以写个博客防止 ...
- Luogu 3369 / BZOJ 3224 - 普通平衡树 - [无旋Treap]
题目链接: https://www.lydsy.com/JudgeOnline/problem.php?id=3224 https://www.luogu.org/problemnew/show/P3 ...
- HNOI2012 永无乡 无旋Treap
题目描述 永无乡包含 nnn 座岛,编号从 111 到 nnn ,每座岛都有自己的独一无二的重要度,按照重要度可以将这 nnn 座岛排名,名次用 111 到 nnn 来表示.某些岛之间由巨大的桥连接, ...
- [转载]无旋treap:从单点到区间(例题 BZOJ1500&NOI2005 维护数列 )
转自ZZH大佬,原文:http://www.cnblogs.com/LadyLex/p/7182631.html 1500: [NOI2005]维修数列 Time Limit: 10 Sec Mem ...
- [转载]无旋treap:从好奇到入门(例题:bzoj3224 普通平衡树)
转载自ZZH大佬,原文:http://www.cnblogs.com/LadyLex/p/7182491.html 今天我们来学习一种新的数据结构:无旋treap.它和splay一样支持区间操作,和t ...
- [您有新的未分配科技点]无旋treap:从好奇到入门(例题:bzoj3224 普通平衡树)
今天我们来学习一种新的数据结构:无旋treap.它和splay一样支持区间操作,和treap一样简单易懂,同时还支持可持久化. 无旋treap的节点定义和treap一样,都要同时满足树性质和堆性质,我 ...
- 【算法学习】Fhq-Treap(无旋Treap)
Treap——大名鼎鼎的随机二叉查找树,以优异的性能和简单的实现在OIer们中广泛流传. 这篇blog介绍一种不需要旋转操作来维护的Treap,即无旋Treap,也称Fhq-Treap. 它的巧妙之处 ...
- 无旋treap的区间操作实现
最近真的不爽...一道维修数列就做了我1上午+下午1h+1晚上+晚上1h+上午2h... 一道不错的自虐题... 由于这一片主要讲思想,代码我放这里了 不会无旋treap的童鞋可以进这里 呵呵... ...
- 无旋treap的简单思想以及模板
因为学了treap,不想弃坑去学splay,终于理解了无旋treap... 好像普通treap没卵用...(再次大雾) 简单说一下思想免得以后忘记.普通treap因为带旋转操作似乎没卵用,而无旋tre ...
- [BZOJ3223]文艺平衡树 无旋Treap
3223: Tyvj 1729 文艺平衡树 Time Limit: 10 Sec Memory Limit: 128 MB Description 您需要写一种数据结构(可参考题目标题),来维护一个 ...
随机推荐
- 原根学习笔记+BSGS复习笔记
学原根发现拔山盖世算法忘光了,干脆一块儿写了吧. \(BSGS\) 算法 \(BSGS\) 算法,又名拔山盖世算法.北上广深算法.他解决的问题如下: 求解最小的可行的 \(k\),满足 \(a^k\e ...
- 发那科机器人R2000iC齿轮箱维修方法步骤归纳
一.发那科机器人R2000iC齿轮箱常见故障类型及原因 齿轮磨损:长时间的重载工作或润滑不良可能导致齿轮磨损,表现为噪音增大.振动加剧等. 轴承故障:轴承承受了齿轮箱的径向和轴向载荷,其故障可能导致齿 ...
- Java进阶 - [1-4] 反射
一.类加载区别 当我们刚接触java语言的时候,我们最常见的代码应该就是初始化某个对象,然后调用该对象的方法. 1.使用new创建对象,返回对象的引用.Student student = new St ...
- HTTP协议与RESTful API实战手册(二):用披萨店故事说透API设计奥秘 🍕
title: HTTP协议与RESTful API实战手册(二):用披萨店故事说透API设计奥秘 date: 2025/2/27 updated: 2025/2/27 author: cmdragon ...
- Linux系列:如何调试 malloc 的底层源码
一:背景 1. 讲故事 上一篇我们聊过 C# 调用 C 的 malloc 代码来演示heap的内存泄露问题,但要想深入研究得必须把 malloc 的实现库 libc.so 给调试起来,大家都知道在Li ...
- ABAQUS-循环对称条件的详解
概括 anlysis of model that exhibit cyclic symmetry 循环对称分析技术用于Standard求解器. makes it possible to analyze ...
- 【ABAQUS 二次开发笔记】一次获得多个积分点的输出到dat
当使用shell单元进行composite laminate 建模时,可以为每一指定Intergration point 的个数,默认是3个.(abaqus有很多variable可以在intergra ...
- 对接服务升级后仅支持tls1.2,jdk1.7默认使用tls1.0,导致调用失败
背景 如标题所说,我手里维护了一个重要的老项目,使用jdk1.7,里面对接了很多个第三方服务,协议多种多样,其中涉及http/https的,调用方式也是五花八门,比如:commons-httpclie ...
- 初识 PHP 7 源码整体框架
目录 PHP 7 语言的执行原理 编译型语言与解释型语言 PHP 7 语言的执行原理 我们常用的高级语言有很多种,比较出名的有C\C++.Python.PHP.Go.Pascal等.而这些语言根据运行 ...
- 继承中构造方法访问特点--java 进阶day01
1.子类不可以继承父类的构造方法 构造方法的名称必须与类名一致,上图中类名是Zi,而构造方法名是Fu,肯定不行 2.子类在初始化之前,需要对父类初始化 子类在初始化的过程中,很有可能会调用到父类的数据 ...