平衡树(Splay)
平衡树#
不同平衡树#
有许多不同的平衡树
如:替罪羊树,AVI
,红黑树,Treap
,FHQ-Treap
(无旋Treap
),Splay
,SBT
等
其中比较重点的是上述后四种
目前只学习了 Splay
和 Treap
能够较为熟练的打出来的只有 Splay
有关 Splay
#
代码 (luoguP3396) :
#include<bits/stdc++.h>
using namespace std;
inline int read(){
int x=0,f=1;
char ch=getchar();
while(!isdigit(ch)){
if(ch=='-') f=-1;
ch=getchar();
}
while(isdigit(ch)){
x=(x<<1)+(x<<3)+(ch^48);
ch=getchar();
}
return x*f;
}
#define size six
#define next nex
const int N=2e5+5;
int n;
int size[N],son[N][2],f[N],cnt[N],val[N],tot,rt;
struct Splay{
inline void maintain(int x){
size[x]=size[son[x][0]]+size[son[x][1]]+cnt[x];
}
inline bool get(int x){
return x==son[f[x]][1];
}
inline void rotate(int x){
int y=f[x],z=f[y],k=get(x),w=son[x][k^1];
son[y][k]=w,f[w]=y;
son[z][get(y)]=x,f[x]=z;
son[x][k^1]=y,f[y]=x;
maintain(y),maintain(x);
}
inline void splay(int x,int pos){
while(f[x]!=pos){
int y=f[x],z=f[y];
if(z!=pos){
if(get(x)==get(y)) rotate(y);
else rotate(x);
}
rotate(x);
}
if(pos==0) rt=x;
}
inline void insert(int x){
int cur=rt,p=0;
for(;cur&&val[cur]!=x;p=cur,cur=son[cur][x>val[cur]]);
if(cur) ++cnt[cur];
else{
cur=++tot;
if(p) son[p][x>val[p]]=cur;
son[cur][0]=son[cur][1]=0;
f[cur]=p,val[cur]=x;
cnt[cur]=size[cur]=1;
}
splay(cur,0);
}
inline void find(int x){
int cur=rt;
for(;son[cur][x>val[cur]]&&x!=val[cur];cur=son[cur][x>val[cur]]);
splay(cur,0);
}
inline int xth(int x){
int cur=rt;
while(1){
if(son[cur][0]&&x<=size[son[cur][0]]) cur=son[cur][0];
else if(x>size[son[cur][0]]+cnt[cur]){
x-=size[son[cur][0]]+cnt[cur];
cur=son[cur][1];
}
else{
splay(cur,0);
return cur;
}
}
}
inline int pre(int x){
find(x);
if(val[rt]<x) return rt;
int cur=son[rt][0];
while(son[cur][1]) cur=son[cur][1];
return cur;
}
inline int next(int x){
find(x);
if(val[rt]>x) return rt;
int cur=son[rt][1];
while(son[cur][0]) cur=son[cur][0];
return cur;
}
inline void del(int x){
int pr=pre(x),suc=next(x);
splay(pr,0),splay(suc,pr);
int y=son[suc][0];
if(cnt[y]>1){
--cnt[y];
splay(y,0);
}
else son[suc][0]=0;
splay(suc,0);
}
}t;
signed main(){
n=read();
t.insert(100000000);
t.insert(-100000000);
while(n--){
int op=read(),x=read();
if(op==1) t.insert(x);
if(op==2) t.del(x);
if(op==3){
t.find(x);
printf("%d\n",size[son[rt][0]]);
}
if(op==4) printf("%d\n",val[t.xth(x+1)]);
if(op==5) printf("%d\n",val[t.pre(x)]);
if(op==6) printf("%d\n",val[t.next(x)]);
}
}
当然,之后可能会采用结构体的写法
Splay
是平衡树,它是通过 (伸展) 这一操作来维持平衡的
表示通过不断 把 节点旋转到 节点的儿子处
表示把 旋转到根
这样我们可以通过 等一系列操作更方便的执行复杂的插入与修改
具体会在题目中阐述
Splay
有两点重要的地方:
- 它可以维护序列
- 它是
LCT(Link Cut Tree)
的基础
个人比较喜欢 Splay
的原因:好学,方便,比 Treap
运用的范围更广
当然 FHQ-Treap
貌似也能维护序列 (可我不会)
Splay
习题#
P3391 文艺平衡树#
题意:
给你一个长度为 的序列,序列的第 项初始为
会进行 次翻转序列中区间 的操作
最后输出序列
满足
思路:
将序列每个点的位置存入平衡树中 (这样它的中序遍历就是最后的序列)
上述操作可以 也可以递归建树 (像线段树一样)
翻转区间 时我们
这样区间 就在 的左子树中
然后将 的左子树中所有点的左右子树交换就行
但是一次全交换会 T
飞
所以考虑像线段树一样整一个翻转的
同时在每次访问前 更新
这样就可以保证复杂度
P2234 营业额统计#
题意:
有 天,告诉你每天的营业额
我们定义,一天的最小波动值 =
特别地,第一天的最小波动值为第一天的营业额
求最小波动值之和
思路:
没什么特别,Splay
P1486 郁闷的出纳员#
题意:
给你 条命令, 的值 (工资下界)
-
I k
新建一个工资档案,初始工资为 。如果某员工的初始工资低于工资下界,他将立刻离开公司。 -
A k
把每位员工的工资加上 。 -
S k
把每位员工的工资扣除 。 -
F k
查询第 多的工资。
在初始时,可以认为公司里一个员工也没有。
- ,
思路:
考虑到维护 A
操作比较复杂
搞一个增加值 表示当前已经加过 这么多工资
每次插入 的时候
这样树内的每个值 实际上是
由于需要删掉每个小于 的节点
每次删除操作时把大于等于 的最小值 到根,然后把根的左儿子变成
这样就完成了删除操作
注意这里调用 就要把 改成 ,或者是调用
P2286 宠物收养场#
题意:
凡凡开了一间宠物收养场。收养场提供两种服务:收养被主人遗弃的宠物和让新的主人领养这些宠物。
被遗弃的宠物过多时,假若到来一个领养者,这个领养者希望领养的宠物的特点值为 ,那么它将会领养一只目前未被领养的宠物中特点值最接近 的一只宠物。
(任何两只宠物的特点值都不可能是相同的,任何两个领养者的希望领养宠物的特点值也不可能是一样的)
如果存在两只宠物他们的特点值分别为 和 ,那么领养者将会领养特点值为 的那只宠物。
收养宠物的人过多,假若到来一只被收养的宠物,能够领养它的领养者,是那个希望被领养宠物的特点值最接近该宠物特点值的领养者。
如果该宠物的特点值为a,存在两个领养者他们希望领养宠物的特点值分别为 和 ,那么特点值为 的那个领养者将成功领养该宠物。
一个领养者领养了一个特点值为 的宠物,而它本身希望领养的宠物的特点值为 ,那么这个领养者的不满意程度为 。
给你 个 领养者和被收养宠物到来收养所的情况,请你计算所有收养了宠物的领养者的不满意程度的总和。
初始时,收养所里面既没有宠物,也没有领养者。
思路:
维护一棵 Splay
以及当前树内是宠物还是领养者
找前驱后继,若前驱后继都行则选前驱,然后删掉就行
P3850 书架#
题意:
有一个书架上有 本书,给你 本书以及要插入的位置 ,将其插进去
次询问,每次问你第 本书的名字
, ,
思路:
Splay
维护位置
每次插入到位置 就找到在树中排名为 的位置 并
找到在树中排名为 的位置 并
这样 的右儿子为空,我们让 为 的右儿子就完成了插入
每次查找直接问就行 (因为树中的排名就表示序列中的排名)
P3586 LOG
#
题意:
维护一个长度为 的序列,一开始都是 ,支持以下两种操作:
U k a
将序列中第 个数修改为 。Z c s
在这个序列上,每次选出 个正数,并将它们都减去 ,询问能否进行 次操作。
每次询问独立,即每次询问不会对序列进行修改。
思路:
操作 很容易
对于操作 分为大于等于 的数和小于 的数,让它们总贡献大于等于 就行
假设大于等于 的数有 个,那么它们的贡献就是 (每一个数的贡献至多为 )
而小于 的数的贡献就是它们的和
所以能进行 次操作的条件是:
也就是
因此在 Splay
中 的时候多维护一个东西 (这个东西的维护方式就和 一样)
设 ,每次查询的时候 ,那么就可以计算 和 了
注意:代码中 因为最开始插入了一个极大值防止越界
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· winform 绘制太阳,地球,月球 运作规律
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· AI 智能体引爆开源社区「GitHub 热点速览」
· Manus的开源复刻OpenManus初探
· 写一个简单的SQL生成工具