替罪羊树 知识总结
替罪羊树是一类神奇的平衡树。它最神奇的地方就在于,大部分平衡树都是用愚蠢的单旋来维护平衡,而fhq-Treap则是用split和merge维护平衡的,可替罪羊树却是用一种神奇的操作维护平衡的,那就是重构Rebuild。每次插入和删除元素的时候,检查子树大小,若失衡则直接重购以维护整棵树平衡。
在讲思路前,我们先要注意一个点:替罪羊树仅能用重构来维持平衡,因而若节点被删除后,无法将其高效的移除,所以我们采取懒惰删除,每次仅将节点的计数器--即可。
先来看一下我们的变量区和基础的函数吧:
struct node{
int l,r;//左右儿子
int sd,sz;//sd为节点所在子树的实际大小,而sz则为包括所有删除的元素在内的子树大小
int cnt,val;//cnt为数量,val为键值
};
inline void update(int p)
{
f[p].sd=f[f[p].l].sd+f[f[p].r].sd+f[p].cnt;
f[p].sz=f[f[p].l].sz+f[f[p].r].sz+f[p].cnt;
}
接下来我们来看看重构操作吧。我们预先设定一个阈值α,一般取[0.7,0.8]左右,若检查到某个节点的儿子所在子树的sz占这个节点sz的比例超过α时,就重构。由于当被删除节点过多时,搜索树效率也会显著降低,于是当一个节点的sd占其sz的比例超过α时,亦重构。
inline bool Can_Rbu(int p)
{
return f[p].cnt&&(f[p].sz*alpha<=(double)max(f[f[p].l].sz,f[f[p].r].sz)||f[p].sz*alpha>=(double)f[p].sd);
}
那么怎么重构呢?我们已经知道了,对于一棵平衡树,它的中序遍历是排好序的。所以我们把它的中序遍历拉到数组中,然后重新二分建树即可:
int tmp[100010];
void Rbu_Flatten(int &num,int p)
{
if(!p) return;//空儿子返回
Rbu_Flatten(num,f[p].l);//搜左儿子
if(f[p].cnt) tmp[num++]=p;//加入当前节点
Rbu_Flatten(num,f[p].r);//搜右儿子
}
int Rbu_Build(int l,int r)
{
if(l>=r) return 0;//二分边界
int mid=(l+r)>>1;
f[tmp[mid]].l=Rbu_Build(l,mid);//建左儿子
f[tmp[mid]].r=Rbu_Build(mid+1,r);//建右儿子
update(tmp[mid]);//更新信息
return tmp[mid];
}
void Rbu(int &p)
{
int x=0;
Rbu_Flatten(x,p);
p=Rbu_Build(0,x);//这里注意下标从0开始可以节省很多事情
}
以上就是基本函数啦!其余的部分神似普通的平衡树,我们的介绍和注释就稍微简略一些了咯。
首先是插入和删除。我们在遍历路径上每一个节点时,都检查是否满足重构条件,并重构即可。其余与普通二叉搜索时雷同。
void Insert(int &p,int val)
{
if(!p)
{
p=++tot;
f[p].cnt=f[p].sd=f[p].sz=1;
f[p].val=val;
f[p].l=f[p].r=0;
}
else
{
if(f[p].val==val) f[p].cnt++;
else if(val<f[p].val) Insert(f[p].l,val);
else Insert(f[p].r,val);
update(p);
if(Can_Rbu(p)) Rbu(p);
}
}
void del(int &p,int val)
{
if(!p) return;
f[p].sd--;
if(f[p].val==val)
{
if(f[p].cnt) f[p].cnt--;
}
else
{
if(val<f[p].val) del(f[p].l,val);
else del(f[p].r,val);
update(p);
}
if(Can_Rbu(p)) Rbu(p);
}
接下来是由权值查询对应的值。依旧是与普通二叉搜索树完全相同的操作,在此不再赘述了,不懂直接看代码吧QAQ
int GetVal(int p,int rk)
{
if(!p) return 0;
if(rk>f[f[p].l].sd&&rk<=f[f[p].l].sd+f[p].cnt) return f[p].val;
else if(rk>f[f[p].l].sd+f[p].cnt) return GetVal(f[p].r,rk-f[f[p].l].sd-f[p].cnt);
else return GetVal(f[p].l,rk);
}
最后是查询前后驱的函数。在此我们稍微的改变一下函数的返回值:将返回数值改为返回下标。这样以后查询排名就可以用前驱坐标+1即可。代码依旧与普通平衡树相同……
int Get_uprb(int p,int val)
{
if(!p) return 1;
if(f[p].val==val&&f[p].cnt) return f[f[p].l].sd+f[p].cnt+1;
else if(val<f[p].val) return Get_uprb(f[p].l,val);
else return f[f[p].l].sd+f[p].cnt+Get_uprb(f[p].r,val);
}
int Get_lwrb(int p,int val)
{
if(!p) return 0;
if(f[p].val==val&&f[p].cnt) return f[f[p].l].sd;
else if(val<f[p].val) return Get_lwrb(f[p].l,val);
else return f[f[p].l].sd+f[p].cnt+Get_lwrb(f[p].r,val);
}
最后就是完整版代码啦:
#include<bits/stdc++.h>
using namespace std;
template <typename T>
void read(T &x) {
x = 0;
int f = 1;
char ch = getchar();
while (!isdigit(ch)) {
if (ch == '-') f = -1;
ch = getchar();
}
while (isdigit(ch)) {
x = x * 10 + (ch ^ 48);
ch = getchar();
}
x *= f;
return;
}
template <typename T>
void write(T x)
{
if(x < 0) {
putchar('-');
x = -x;
}
if(x > 9)
write(x/10);
putchar(x % 10 + '0');
return;
}
int n;
int rt,tot=0;
struct node{
int l,r;
int sd,sz;
int cnt,val;
}f[100010];
const double alpha=0.75;
inline void update(int p)
{
f[p].sd=f[f[p].l].sd+f[f[p].r].sd+f[p].cnt;
f[p].sz=f[f[p].l].sz+f[f[p].r].sz+f[p].cnt;
}
inline bool Can_Rbu(int p)
{
return f[p].cnt&&(f[p].sz*alpha<=(double)max(f[f[p].l].sz,f[f[p].r].sz)||f[p].sz*alpha>=(double)f[p].sd);
}
int tmp[100010];
void Rbu_Flatten(int &num,int p)
{
if(!p) return;
Rbu_Flatten(num,f[p].l);
if(f[p].cnt) tmp[num++]=p;
Rbu_Flatten(num,f[p].r);
}
int Rbu_Build(int l,int r)
{
if(l>=r) return 0;
int mid=(l+r)>>1;
f[tmp[mid]].l=Rbu_Build(l,mid);
f[tmp[mid]].r=Rbu_Build(mid+1,r);
update(tmp[mid]);
return tmp[mid];
}
void Rbu(int &p)
{
int x=0;
Rbu_Flatten(x,p);
p=Rbu_Build(0,x);
}
void Insert(int &p,int val)
{
if(!p)
{
p=++tot;
f[p].cnt=f[p].sd=f[p].sz=1;
f[p].val=val;
f[p].l=f[p].r=0;
}
else
{
if(f[p].val==val) f[p].cnt++;
else if(val<f[p].val) Insert(f[p].l,val);
else Insert(f[p].r,val);
update(p);
if(Can_Rbu(p)) Rbu(p);
}
}
void del(int &p,int val)
{
if(!p) return;
f[p].sd--;
if(f[p].val==val)
{
if(f[p].cnt) f[p].cnt--;
}
else
{
if(val<f[p].val) del(f[p].l,val);
else del(f[p].r,val);
update(p);
}
if(Can_Rbu(p)) Rbu(p);
}
int Get_uprb(int p,int val)
{
if(!p) return 1;
if(f[p].val==val&&f[p].cnt) return f[f[p].l].sd+f[p].cnt+1;
else if(val<f[p].val) return Get_uprb(f[p].l,val);
else return f[f[p].l].sd+f[p].cnt+Get_uprb(f[p].r,val);
}
int Get_lwrb(int p,int val)
{
if(!p) return 0;
if(f[p].val==val&&f[p].cnt) return f[f[p].l].sd;
else if(val<f[p].val) return Get_lwrb(f[p].l,val);
else return f[f[p].l].sd+f[p].cnt+Get_lwrb(f[p].r,val);
}
int GetVal(int p,int rk)
{
if(!p) return 0;
if(rk>f[f[p].l].sd&&rk<=f[f[p].l].sd+f[p].cnt) return f[p].val;
else if(rk>f[f[p].l].sd+f[p].cnt) return GetVal(f[p].r,rk-f[f[p].l].sd-f[p].cnt);
else return GetVal(f[p].l,rk);
}
int main()
{
read(n);
for(int i=1;i<=n;i++)
{
int op;
read(op);
int x;
read(x);
switch(op)
{
case 1:
Insert(rt,x);break;
case 2:
del(rt,x);break;
case 3:
write(Get_lwrb(rt,x)+1);putchar('\n');break;
case 4:
write(GetVal(rt,x));putchar('\n');break;
case 5:
write(GetVal(rt,Get_lwrb(rt,x)));putchar('\n');break;
case 6:
write(GetVal(rt,Get_uprb(rt,x)));putchar('\n');break;
}
}
return 0;
}