听说 lemon 不太资瓷指针和动态开内存,所以学了一下数组非递归版的伸展树,简单记一下。(没有仔细写,只是为了方便复习,所以并不是一篇入门教程)
以模版题 普通平衡树 为例。
维护信息:
int rt,ch[maxn][2],fa[maxn],v[maxn],c[maxn],sz[maxn],tot=0;
inline void mt(int x) {sz[x]=sz[ch[x][0]]+c[x]+sz[ch[x][1]];}
核心操作
旋转操作:将下标为 x x x 的节点旋转到其父节点的位置。
inline void rotate(int x) {
int f=fa[x],ff=fa[f],k=ch[f][1]==x;
ch[ff][ch[ff][1]==f]=x;fa[x]=ff;
ch[f][k]=ch[x][k^1];fa[ch[x][k^1]]=f;
ch[x][k^1]=f;fa[f]=x;mt(f);mt(x);
}
步骤:
- 记录父节点,祖先节点以及当前节点是父节点的哪个儿子。
- 将祖父节点与 x x x 相连。
- 将 x x x 的父节点与 x x x 的子节点相连。
- 将 x x x 与父节点相连。
- 先维护父节点,再维护 x x x。(自下而上)
伸展操作:将 x x x 伸展到 g g g 的子节点。
inline void splay(int x,int g) {
while(fa[x]!=g) {
int f=fa[x],ff=fa[f];
if(ff!=g) (ch[f][0]==x)^(ch[ff][0]==f)?rotate(x):rotate(f);
rotate(x);
}
if(!g) rt=x;
}
步骤:
- 持续循环直到 x x x 的父节点为 g g g:
- 记录 x x x 的父节点和祖父节点。
- 如果祖父节点不为 g g g(父节点不为目标节点),则需要旋转两次,此时若不在同一直线上则旋转 x x x,否则旋转其父节点。
- 无论如何都要旋转一次 x x x。
- 如果 g g g 是 0 0 0,更新根节点。
基本操作
插入节点:插入值为 x x x 的节点。
inline void insert(int x) {
int u=rt,f=0;
while(u&&v[u]!=x) {f=u;u=ch[u][x>v[u]];}
if(u) ++c[u];
else {
u=++tot;ch[u][0]=ch[u][1]=0;fa[u]=f;v[u]=x;c[u]=sz[u]=1;
if(f) ch[f][x>v[f]]=u;
}
splay(u,0);
}
步骤:
- 定义两个变量分别保存当前节点与其父节点。
- 一直循环向子节点查找直到找到或到达空节点。
- 如果节点不为空,则直接增加上面的个数。
- 否则新建一个节点,同时一直维护的父节点更新其子节点
- 将新插入的节点伸展到根节点。
查找节点:查找值为 x x x 的节点,若未找到则返回一个其附近的节点,将找到的节点伸展到根。
inline void find(int x) {
int u=rt;if(!u) return;
while(ch[u][x>v[u]]&&x!=v[u]) u=ch[u][x>v[u]];
splay(u,0);
}
步骤:
- 记录当前节点,判空直接返回。
- 循环找出最后一个不为空的节点,若中途找到跳出循环。
- 将找到的节点伸展到根节点。
查找区间第 k k k 大。
inline int kth(int k) {
int u=rt;if(sz[u]<k) return 0;
while(1) {
int lc=ch[u][0];
if(k<=sz[lc]) u=lc;
else if(k<=sz[lc]+c[u]) return v[u];
else {k-=sz[lc]+c[u];u=ch[u][1];}
}
}
判空一下后面比较简单不说了。
查找前驱和后继。
inline int pre(int x) {
find(x);int u=rt;if(v[u]<x) return u;
u=ch[u][0];while(ch[u][1]) u=ch[u][1];
return u;
}
inline int suf(int x) {
find(x);int u=rt;if(v[u]>x) return u;
u=ch[u][1];while(ch[u][0]) u=ch[u][0];
return u;
}
步骤:
- 用
find
函数查找节点,若在其前/后直接返回,否则找相反子树最靠近当前位置的节点。
删除节点:删除值为 x x x 的节点。
inline void remove(int x) {
int lst=pre(x),nxt=suf(x);splay(lst,0);splay(nxt,lst);
int del=ch[nxt][0];
if(c[del]>1) {--c[del];splay(del,0);}
else ch[nxt][0]=0;
}
步骤:
- 查找前驱后继后伸展使得满足条件的节点被放置在一棵子树中。
- 如果节点中的个数大于 1 1 1 直接减一,然后伸展到根节点。
- 否则从其父节点删除对它的索引。