在网上某篇神奇的教程和@codesonic 大佬的标程帮助下,我又肝完了Leafy Tree,跑过来写篇题解(好像以前写过一篇?)


什么是Leafy Tree?

Leafy Tree由两种节点组成:辅助节点与叶子节点。

叶子节点储存值,而辅助节点储存左右孩子中大的那个值。

注意:辅助节点必定有两个孩子。

操作如何实现?

拿插入操作举例:

一路向下递归,每次拿左子树最大值与插入值作比较,如果大就往左,如果小就往右。

到底了就插入叶子与辅助。

然后再回溯更新。

这时候就会出一个问题:这个算法很容易被数据卡。

解决方案是引入平衡因子,在适当的时候重建这颗树。

重构方法可以拍扁也可以旋转。

拍扁的方法就是中序遍历一遍然后重新建树(具体可以参考这里),旋转的一会儿会讲。


工具函数

这里是一些简单的重要的工具函数。

  1. 新建节点
inline void newNode(int &pos,int v){
pos=++cnt,size[pos]=1,val[pos]=v;
}

cnt是总结点个数,size是子树大小,val是值(废话)。

  1. 复制节点
inline void copyNode(int x,int y){
size[x]=size[y],ls[x]=ls[y],rs[x]=rs[y],val[x]=val[y];
}

没什么好说的

  1. 合并节点
void merge(int l,int r){
size[++cnt]=size[l]+size[r],val[cnt]=val[r],ls[cnt]=l,rs[cnt]=r;
}
  1. 旋转
void rotate(int pos,bool flag){
if(flag){
merge(ls[pos],ls[rs[pos]]);
ls[pos]=cnt,rs[pos]=rs[rs[pos]];
}else{
merge(rs[ls[pos]],rs[pos]);
rs[pos]=cnt,ls[pos]=ls[ls[pos]];
}
}

这是重建依赖的旋转函数,左旋右旋看flag。

具体流程没什么好讲的,可以参考splay的左旋与右旋。

  1. 重建
void maintain(int pos){
if(size[ls[pos]]>size[rs[pos]]*alpha)rotate(pos,0);
else if(size[rs[pos]]>size[ls[pos]]*alpha)rotate(pos,1);
if(size[ls[pos]]>size[rs[pos]]*alpha)rotate(ls[pos],1),rotate(pos,0);
else if(size[rs[pos]]>size[ls[pos]]*alpha)rotate(rs[pos],0),rotate(pos,1);
}

这是重建函数,平衡因子就这题而言取4应该是最快的。


插入操作

void insert(int pos,int v){
if(size[pos]==1){
newNode(ls[pos],min(v,val[pos]));
newNode(rs[pos],max(v,val[pos]));
pushup(pos);
return;
}
if(v>val[ls[pos]])insert(rs[pos],v);
else insert(ls[pos],v);
pushup(pos);
maintain(pos);
}

思路就是之前讲的一路向下递归。

当子树大小为1的时候(到头了)就在底下新建两个节点,一个叶子一个辅助,然后回溯更新。

如果没到头的话就继续递归,然后递归完了就维护一下左右子树的平衡,看看需不需要重建。


删除操作

void erase(int pos,int v){
if(size[pos]==1){
if(ls[father]==pos)copyNode(father,rs[father]);
else copyNode(father,ls[father]);
return;
}
father=pos;
if(v>val[ls[pos]])erase(rs[pos],v);
else erase(ls[pos],v);
pushup(pos);
maintain(pos);
}

删除操作的思路和插入操作一样,一路向下。

需要说明的是father是我们记录的父亲(也可以不这样而是通过传参解决)。


排名查询&排名对应数查询

int kth(int pos,int v){
if(size[pos]==v)return val[pos];
if(v>size[ls[pos]])return kth(rs[pos],v-size[ls[pos]]);
return kth(ls[pos],v);
}
int rank(int pos,int v){
if(size[pos]==1)return 1;
if(v>val[ls[pos]])return rank(rs[pos],v)+size[ls[pos]];
return rank(ls[pos],v);
}

这两个。。。写什么平衡树都会用到肯定大家都会。


然后好像就结束了(Leafy Tree本来码量就不高)

不开O2不加读优大概是311ms左右(竟然还没有我替罪羊树快),应该是写丑了吧。。。

代码如下:

#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
const int N=100100;
const int alpha=4;
int n,cnt,father,root;
int val[N<<2],size[N<<2],ls[N<<2],rs[N<<2];
inline void newNode(int &pos,int v){
pos=++cnt,size[pos]=1,val[pos]=v;
}
inline void copyNode(int x,int y){
size[x]=size[y],ls[x]=ls[y],rs[x]=rs[y],val[x]=val[y];
}
void merge(int l,int r){
size[++cnt]=size[l]+size[r],val[cnt]=val[r],ls[cnt]=l,rs[cnt]=r;
}
void rotate(int pos,bool flag){
if(flag){
merge(ls[pos],ls[rs[pos]]);
ls[pos]=cnt,rs[pos]=rs[rs[pos]];
}else{
merge(rs[ls[pos]],rs[pos]);
rs[pos]=cnt,ls[pos]=ls[ls[pos]];
}
}
void maintain(int pos){
if(size[ls[pos]]>size[rs[pos]]*alpha)rotate(pos,0);
else if(size[rs[pos]]>size[ls[pos]]*alpha)rotate(pos,1);
if(size[ls[pos]]>size[rs[pos]]*alpha)rotate(ls[pos],1),rotate(pos,0);
else if(size[rs[pos]]>size[ls[pos]]*alpha)rotate(rs[pos],0),rotate(pos,1);
}
void pushup(int pos){
if(!size[ls[pos]])return;
size[pos]=size[ls[pos]]+size[rs[pos]];
val[pos]=val[rs[pos]];
}
void insert(int pos,int v){
if(size[pos]==1){
newNode(ls[pos],min(v,val[pos]));
newNode(rs[pos],max(v,val[pos]));
pushup(pos);
return;
}
if(v>val[ls[pos]])insert(rs[pos],v);
else insert(ls[pos],v);
pushup(pos);
maintain(pos);
}
void erase(int pos,int v){
if(size[pos]==1){
if(ls[father]==pos)copyNode(father,rs[father]);
else copyNode(father,ls[father]);
return;
}
father=pos;
if(v>val[ls[pos]])erase(rs[pos],v);
else erase(ls[pos],v);
pushup(pos);
maintain(pos);
}
int kth(int pos,int v){
if(size[pos]==v)return val[pos];
if(v>size[ls[pos]])return kth(rs[pos],v-size[ls[pos]]);
return kth(ls[pos],v);
}
int rank(int pos,int v){
if(size[pos]==1)return 1;
if(v>val[ls[pos]])return rank(rs[pos],v)+size[ls[pos]];
return rank(ls[pos],v);
}
int main(){
scanf("%d",&n);
newNode(root,2147483647);
while(n--){
int s,a;
scanf("%d%d",&s,&a);
if(s==1)insert(root,a);
if(s==2)erase(root,a);
if(s==3)printf("%d\n",rank(root,a));
if(s==4)printf("%d\n",kth(root,a));
if(s==5)printf("%d\n",kth(root,rank(root,a)-1));
if(s==6)printf("%d\n",kth(root,rank(root,a+1)));
}
}

题解 P3369 【【模板】普通平衡树】的更多相关文章

  1. luoguP3391[模板]文艺平衡树(Splay) 题解

    链接一下题目:luoguP3391[模板]文艺平衡树(Splay) 平衡树解析 这里的Splay维护的显然不再是权值排序 现在按照的是序列中的编号排序(不过在这道题目里面就是权值诶...) 那么,继续 ...

  2. luoguP3369[模板]普通平衡树(Treap/SBT) 题解

    链接一下题目:luoguP3369[模板]普通平衡树(Treap/SBT) 平衡树解析 #include<iostream> #include<cstdlib> #includ ...

  3. 2021.07.02 P1383 高级打字机题解(可持久化平衡树)

    2021.07.02 P1383 高级打字机题解(可持久化平衡树) 分析: 从可以不断撤销并且查询不算撤销这一骚操作可以肯定这是要咱建一棵可持久化的树(我也只会建可持久化的树,当然,还有可持久化并查集 ...

  4. 【题解】二逼平衡树 [P3380] [BZOJ3196] [Tyvj1730]

    [题解]二逼平衡树 [P3380] [BZOJ3196] [Tyvj1730] 传送门:[模板]二逼平衡树(树套树)\([P3380]\) \([BZOJ3196]\) \([TYVJ1730]\) ...

  5. 题解 P3369 【【模板】普通平衡树(Treap/SBT)】

    STL真是个好东西. 最近在看pb_ds库及vector和set的用法,就想用这三种操作来实现一下普通平衡树,结果pb_ds中的rbtree不支持重复值,而本蒟蒻也看不懂不懂各大佬用pb_ds的实现, ...

  6. 【洛谷P3369】 (模板)普通平衡树

    https://www.luogu.org/problemnew/show/P3369 Splay模板 #include<iostream> #include<cstdio> ...

  7. [题解]bzoj 3223 文艺平衡树

    3223: Tyvj 1729 文艺平衡树 Time Limit: 10 Sec  Memory Limit: 128 MBSubmit: 3884  Solved: 2235[Submit][Sta ...

  8. [luogu3369/bzoj3224]普通平衡树(splay模板、平衡树初探)

    解题关键:splay模板题整理. 如何不加入极大极小值?(待思考) #include<cstdio> #include<cstring> #include<algorit ...

  9. 【模板】平衡树——Treap和Splay

    二叉搜索树($BST$):一棵带权二叉树,满足左子树的权值均小于根节点的权值,右子树的权值均大于根节点的权值.且左右子树也分别是二叉搜索树.(如下) $BST$的作用:维护一个有序数列,支持插入$x$ ...

随机推荐

  1. BootStrap复习总结

    Bootstrap3 复习总结: 1. 栅格系统: sm:768px md:992px lg:1200px (响应式布局) 1.横向分割页面的空间. 2.在分割的同时考虑到响应式. 内部实现是12列的 ...

  2. laravel :Call to undefined function App\Http\Controllers\success() 解决方法

    今天在调用方法时,报错如下:Call to undefined function App\Http\Controllers\success():方法已定义好了,所以我怀疑是未引入function.ph ...

  3. vivo输入法 需求分析

    我使用的输入法:vivo输入法 1,用户界面:界面为白色和灰色,整体简洁大方,个人而言外观挺不错.但是不能自定义界面,更改背景图片或是主题. 2,记住用户选择:可记忆上次使用后的键盘方式(26键或是9 ...

  4. 浅谈密码加SALT原理

    我们知道,如果直接对密码进行散列,那么黑客可以对通过获得这个密码散列值,然后通过查散列值字典(例如MD5密码破解网站),得到某用户的密码.  加Salt可以一定程度上解决这一问题.所谓加Salt方法, ...

  5. 【codeforces 757E】Bash Plays with Functions

    [题目链接]:http://codeforces.com/problemset/problem/757/E [题意] 给你q个询问; 每个询问包含r和n; 让你输出f[r][n]; 这里f[0][n] ...

  6. Git学习总结(9)——如何构建你自己的 Git 服务器

    现在我们将开始学习如何构建一个Git服务器,如何在具体的事件中写一个针对特定的触发操作的自定义Git(例如通告),如何发布你的代码到一个网站. 目前为止,用户对Git的焦点主要在Git的使用上.这篇文 ...

  7. java io包File类

    1.java io包File类, Java.io.File(File用于管理文件或目录: 所属套件:java.io)1)File对象,你只需在代码层次创建File对象,而不必关心计算机上真正是否存在对 ...

  8. [Ubuntu]安装中文输入法

    安装了英文版的Ubuntu12.04之后.使用ctrt+space无法切换到中文,才反应过来. 于是查询了下怎么在Ubuntu下安装和配置中文输入法. 第一步安装语言包: 在左側的菜单条中点击 sys ...

  9. 闭包(closure)与协程共用时要注意的事情

    闭包是一种能够让你用非常舒服的方式来编程的小技巧,Go也支持闭包. 假设从来没有接触过闭包,想在一開始就弄懂什么是闭包(closure)是非常困难的,就像递归一样,直到你真正写过.用过它,你才干真正的 ...

  10. Web前端国际化之jQuery.i18n.properties

    Web前端国际化之jQuery.i18n.properties jQuery.i18n.properties介绍 国际化是如今Web应用程序开发过程中的重要一环,jQuery.i18n.propert ...