《平衡树》读后感
第一框,世界属于fhq-treap
是什么?
你说的对,但是《fhq-treap》是由范浩强自主研发的一款全新树形数据结构。数据结构发生在一个被称作「二叉搜索树」的幻想世界,在这里,被人创造的节点将被授予「随机优先级」,导引期望 \(O(\log n)\) 之力。treap 将扮演一位名为「根据优先级建最小堆,根据权值建搜索树」的神秘角色,在自由的调试中邂逅性格各异、能力独特的同伴们——「分裂、合并、插训排名、查询 k 小值、插入、删除、查询前驱\后继」,和他们一起击败难题,找回出题人的木琴——同时,逐步发掘「大常数」的真相。
怎么做?
$$新建节点与基本定义$$
每一个点有以下几个变量:
struct zxc{
int siz;//子树大小
int son[2];//son[0]表示左儿子、son[1]表示右儿子
int v;//权值
int pri;//随机赋予的优先级
}tr[100100];
除此以外,我们还需要 root 表示根(初始为0) 和 tot 表示节点数(初始为0)
treap 的复杂度保障建立于随机优先级,简单来说,当 a 点的优先级小于 b 点的优先级时,a 就是 b 的儿子,不过现在用不着。
新建一个权值为 v 的节点代码如下:
int nw(int v){
siz(++tot)=1;
v(tot)=v;
pri(tot)=rand();
return tot;
}
顺便处理一下上传 siz 的函数:
void up(int x){
siz(x)=1+siz(ls(x))+siz(rs(x));
return;
}
$$合并$$
合并要实现的功能非常好理解,就是把两个节点的子树用神奇强力胶粘到一起。
但是如果你要自己处理这个过程,就会遇到些麻烦,如图所示:
所以我们利用一个递归来帮助实现过程,这样只用把程序丢到一边,让它自己分讨就可以了
int merge(int x,int y){
if(!x||!y){//若递归出界了、就直接返回另一个
return x+y;//因为有一个是0,所以可以偷一下懒,x+y就是非零的那一个
}
if(pri(x)<pri(y)){//y要成为x的儿子
rs(x)=merge(rs(x),y);//这里的rs和ls都可以,但这是一个习惯,实际上因为需要将merge和split的处理统一化、就一般(至少对我来说)把y放到x的右儿子
up(x);//上传siz
return x;
}
else{//以下同理
ls(y)=merge(x,ls(y));
up(y);
return y;
}
}
$$分裂$$
比较简单,只需要找到需要割的位置,然后割开就可以了(这好像是废话)
分为按权值割和按子树大小割。
按权值割:
void split(int now,int v,int &x,int &y){
if(!now){
x=y=0;//砍下去!
return;
}
if(v(now)<=v){//若当前的权值小就往右走
x=now;//这里和merge同理,实际上x或y都可以
split(rs(now),v,rs(now),y);
}
else{
y=now;
split(ls(now),v,x,ls(now));
}
up(now);
return;
}
按子树大小割:
void split(int now,int v,int &x,int &y){
if(!now){
x=y=0;//砍下去!
return;
}
if(siz(ls(now))>=v){//若这个点和左子树的总和太大就往左走
y=now;//这里和merge同理,实际上x或y都可以
split(ls(now),v,x,ls(now));
}
else{
x=now;
split(rs(now),v-siz(ls(now))-1,rs(now),y);
}
up(now);
return;
}
$$查询排名为k的数$$
得益于二叉搜索树的性质,此操作可以很轻松的完成(如果你仔细想想就会发现,二叉搜索树的名字也可能来源于这一优点),如图所示:
显然可以看出,当前排名小就往右走,大就往左走,顺便统计比 now 大的个数就可以了。
代码如下:
int kth(int now,int k){
while(1){
if(k<siz(ls(now))+1){//大了就往左
now=ls(now);
}
else{
if(k==siz(ls(now))+1){//完全一样
return v(now);
}
k-=siz(ls(now))+1;//偷懒直接改变k,可以省一个变量
now=rs(now);//小了就往右
}
}
}
$$插入节点$$
正常来说、在二叉搜索树中插入一个节点需要从上往下遍历一遍,但有了分裂这样方便的操作,就只需要在需要插入的地方砍一刀就可以了。
代码如下:
void insert(int v){
int x=0,y=0;
split(root,v,x,y);//在需要插入的地方砍一刀
root=merge(merge(x,nw(v)),y);//新建之后把一切粘到一块
return;
}
$$删除$$
因为可能有同样的权值,所以要稍微麻烦一点,砍两刀之后合并然后把中间那一段丢到太空中就可以了。
代码如下:
void delet(int v){
int x=0,y=0,z=0;
split(root,v,x,y);//在v砍一刀
split(x,v-1,x,z);//在v-1砍一刀
z=merge(ls(z),rs(z));//z这个点不要,通过这个方法舍弃
root=merge(merge(x,z),y);//合并两边
return;
}
$$查询~x~的排名$$
砍一刀把比 x 小的扔掉然后再查询子树大小就可以了
代码如下:
int rak(int v){
int x=0,y=0,ans=0;
split(root,v-1,x,y);//先砍一刀
ans=siz(x)+1;//注意排名要+1
merge(x,y);//最后记得缝回去
return ans;
}
$$查询前驱/后继$$
前驱要在 \(v-1\) 砍一刀,然后在全部 \(> v\) 的子树内找最小即可
int nxt(int v){
int x=0,y=0,ans=0;
split(root,v-1,x,y);//砍一刀
if(!x){//若不存在更小返回极小值
return -2147483647;
}
ans=kth(x,siz(x));//简单粗暴的查询最小值的方法()
merge(x,y);//还是缝回去
return ans;
}
后继较为类似,在 \(v\) 砍一刀,然后在全部 \(< v\) 的子树中找最大即可
int lst(int v){
int x=0,y=0,ans=0;
split(root,v,x,y);//砍一刀
if(!y){//若不存在更大返回极大值
return 2147483647;
}
ans=kth(y,1);//简单粗暴的查询最大值的方法()
merge(x,y);//还是缝回去
return ans;
}
P3369ACcode
代码有些长,压缩了一下,点击查看
#include<bits/stdc++.h>
#define int long long
using namespace std;
struct Fhq{
int tot=0,root=0;
struct zxc{
int siz,son[2],v,pri;
}tr[2000100];
#define ls(x) tr[x].son[0]
#define rs(x) tr[x].son[1]
#define siz(x) tr[x].siz
#define pri(x) tr[x].pri
#define v(x) tr[x].v
void up(int x){
siz(x)=siz(ls(x))+siz(rs(x))+1;
return;
}
int nw(int v){
v(++tot)=v;
siz(tot)=1;
pri(tot)=rand();
return tot;
}
int kth(int now,int k){
while(1){
if(k<siz(ls(now))+1){
now=ls(now);
}
else{
if(siz(ls(now))+1==k){
return v(now);
}
k-=siz(ls(now))+1;
now=rs(now);
}
}
}
void split(int now,int v,int &x,int &y){
if(!now){
x=y=0;
return;
}
if(v(now)<=v){
x=now;
split(rs(now),v,rs(now),y);
}
else{
y=now;
split(ls(now),v,x,ls(now));
}
up(now);
return;
}
int merge(int x,int y){
if(!x||!y){
return x+y;
}
if(pri(x)<pri(y)){
rs(x)=merge(rs(x),y);
up(x);
return x;
}
else{
ls(y)=merge(x,ls(y));
up(y);
return y;
}
}
void insert(int v){
int x=0,y=0;
split(root,v,x,y);
root=merge(merge(x,nw(v)),y);
return;
}
void delet(int v){
int x=0,y=0,z=0;
split(root,v,x,y);
split(x,v-1,x,z);
z=merge(ls(z),rs(z));
root=merge(merge(x,z),y);
return;
}
int rak(int v){
int x=0,y=0,ans=0;
split(root,v-1,x,y);
ans=siz(x)+1;
merge(x,y);
return ans;
}
int nxt(int v){
int x=0,y=0,ans=0;
split(root,v-1,x,y);
if(!x){
return -2147483647;
}
ans=kth(x,siz(x));
merge(x,y);
return ans;
}
int lst(int v){
int x=0,y=0,ans=0;
split(root,v,x,y);
if(!y){
return 2147483647;
}
ans=kth(y,1);
merge(x,y);
return ans;
}
}fhq;
signed main(){
int T;
cin>>T;
while(T--){
int opt,x;
cin>>opt>>x;
if(opt==1){
fhq.insert(x);
}
if(opt==2){
fhq.delet(x);
}
if(opt==3){
cout<<fhq.rak(x)<<endl;
}
if(opt==4){
cout<<fhq.kth(fhq.root,x)<<endl;
}
if(opt==5){
cout<<fhq.nxt(x)<<endl;
}
if(opt==6){
cout<<fhq.lst(x)<<endl;
}
}
}
为什么?
不会笛卡尔数,挂个链接走人。
本文作者:LEWISAK
本文链接:https://www.cnblogs.com/lewisak/p/18615931
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步