平衡树

  • 平衡树作为一种中级数据结构,有着广泛的使用场景。其平衡性的维护方式灵活多变,而其中的无旋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中带入的第一棵树值都比第二颗小
}
}

具体操作

  • 讲了这么多,实际上与开头具体的操作关系并不大。现在,就看看如何用以上两个函数实现这些操作
  1. 插入

    先新建一个节点,再按这个点的值进行分裂。这时,其中一个子树中所有值都小于等于新建节点的值。依次合并回去就好了
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重新赋值
  1. 删除

    按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);
  1. 查找x的排名

    按x-1分裂,直接输出较小的树的siz即可
int lr,rr;
split(root,lr,rr,x-1);
printf("%d\n",tr[lr].siz+1);
root=merge(lr,rr);
  1. 查找排名为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);
  1. 求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);
  1. 求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)的更多相关文章

  1. fhq Treap(无旋Treap)

    先吹一波fhq dalao,竟然和我一个姓,我真是给他丢脸. 昨天treap就搞了一下午,感觉自己弱爆了.然后今天上午又看了一个上午的无旋treap再次懵逼,我太弱了,orzorz. 所以写个博客防止 ...

  2. Luogu 3369 / BZOJ 3224 - 普通平衡树 - [无旋Treap]

    题目链接: https://www.lydsy.com/JudgeOnline/problem.php?id=3224 https://www.luogu.org/problemnew/show/P3 ...

  3. HNOI2012 永无乡 无旋Treap

    题目描述 永无乡包含 nnn 座岛,编号从 111 到 nnn ,每座岛都有自己的独一无二的重要度,按照重要度可以将这 nnn 座岛排名,名次用 111 到 nnn 来表示.某些岛之间由巨大的桥连接, ...

  4. [转载]无旋treap:从单点到区间(例题 BZOJ1500&NOI2005 维护数列 )

    转自ZZH大佬,原文:http://www.cnblogs.com/LadyLex/p/7182631.html 1500: [NOI2005]维修数列 Time Limit: 10 Sec  Mem ...

  5. [转载]无旋treap:从好奇到入门(例题:bzoj3224 普通平衡树)

    转载自ZZH大佬,原文:http://www.cnblogs.com/LadyLex/p/7182491.html 今天我们来学习一种新的数据结构:无旋treap.它和splay一样支持区间操作,和t ...

  6. [您有新的未分配科技点]无旋treap:从好奇到入门(例题:bzoj3224 普通平衡树)

    今天我们来学习一种新的数据结构:无旋treap.它和splay一样支持区间操作,和treap一样简单易懂,同时还支持可持久化. 无旋treap的节点定义和treap一样,都要同时满足树性质和堆性质,我 ...

  7. 【算法学习】Fhq-Treap(无旋Treap)

    Treap——大名鼎鼎的随机二叉查找树,以优异的性能和简单的实现在OIer们中广泛流传. 这篇blog介绍一种不需要旋转操作来维护的Treap,即无旋Treap,也称Fhq-Treap. 它的巧妙之处 ...

  8. 无旋treap的区间操作实现

    最近真的不爽...一道维修数列就做了我1上午+下午1h+1晚上+晚上1h+上午2h... 一道不错的自虐题... 由于这一片主要讲思想,代码我放这里了 不会无旋treap的童鞋可以进这里 呵呵... ...

  9. 无旋treap的简单思想以及模板

    因为学了treap,不想弃坑去学splay,终于理解了无旋treap... 好像普通treap没卵用...(再次大雾) 简单说一下思想免得以后忘记.普通treap因为带旋转操作似乎没卵用,而无旋tre ...

  10. [BZOJ3223]文艺平衡树 无旋Treap

    3223: Tyvj 1729 文艺平衡树 Time Limit: 10 Sec  Memory Limit: 128 MB Description 您需要写一种数据结构(可参考题目标题),来维护一个 ...

随机推荐

  1. C# 集成 DeepSeek 模型实现 AI 私有化(本地部署与 API 调用教程)

    前言 自从 DeepSeek 大模型火了以来,网络上出现了许多关于本地部署的教程和方法.然而,要真正深入了解其功能和应用,还是需要自己动手进行一次本地部署. DeepSeek 作为一个高效的自然语言处 ...

  2. WebKit 简介及工作流程

    一.引言 WebKit 是一个被广泛应用于众多知名浏览器的开源网页渲染引擎.它在现代网页浏览体验中扮演着至关重要的角色. 二.WebKit 简介 WebKit 具有高效.灵活和跨平台的特点.它支持多种 ...

  3. Week09_day05(Hbase的安装搭建)

    搭建完全分布式集群 HBase集群建立在hadoop集群基础之上,所以在搭建HBase集群之前需要把Hadoop集群搭建起来,并且要考虑二者的兼容性.现在就以5台机器为例,搭建一个简单的集群. 软件版 ...

  4. mysql扫描全表更新状态部分失败

    1. mysql排序问题 一直以为mysql是按照主键排序的,实则排序和主键没有关系(不使用 order by 子句). 然后从 stackoverflow 上查了一下,找到了以下的回答: 没有默认的 ...

  5. .NET 9.0 全栈技术的高效开源低代码平台(Vue3+Element-Plus)

    前言 推荐一款基于.NET 9.0 全栈框架的新一代技术架构(Vue3+Element-Plus),开源低代码平台-Microi吾码. 平台不仅无限制地支持用户数.表单数.数据量及数据库数量,还通过跨 ...

  6. 【译】Visual Studio 中新的强大生产力特性

    有时候,生活中的小事才是最重要的.在最新版本的 Visual Studio 中,我们增加了一些功能和调整,目的是让您脸上带着微笑,让您更有效率.这里是其中的一些列表,如果您想要完整的列表,请查看发行说 ...

  7. rust学习笔记(4)

    流程控制 if if n < 0 { print!("{} is negative", n); } else if n > 0 { print!("{} is ...

  8. 解决 /usr/bin/env: php: No such file or directory 问题

    前言 composer 报错 env: php: No such file or directory 找不到 php 的执行文件,原因是脚本文件 env 会通过 $PATH 所指定的路径去寻找 php ...

  9. mac更新本地时间

    前言 选取苹果菜单 >"系统偏好设置",然后点按"日期与时间". 点按窗口角落处的锁形图标 ,然后输入您的管理员密码以解锁设置. 在"日期与时 ...

  10. mongodb 查看、创建、修改、删除索引

    简介 索引通常能够极大的提高查询的效率,如果没有索引,MongoDB在读取数据时必须扫描集合中的每个文件并选取那些符合查询条件的记录. 这种扫描全集合的查询效率是非常低的,特别在处理大量的数据时,查询 ...