平衡树--替罪羊树 *
平衡树–替罪羊树
–yangkai
身为平衡树却不做任何形式的旋转,替罪羊树可以称得上是最暴力的平衡树了。
替罪羊树(SGT)保留有二叉搜索树的基本性质,即对于任意一个节点t,左儿子的所有节点比它小,右儿子的所有节点比它大。但是既然不基于翻转,它怎样维护平衡树的优秀复杂度呢?
SDT基于一个叫做“重构”的操作,听起来很是优美暴力,那么我们要如何重构?
首先,如果要时刻维持平衡树的平衡,即,显然我们平均每两次插入、删除操作就需要一次重构,这样的时间效率接近,所以我们可以取一个阈值[0.5,1.0],当且仅当左右子树超过阈值的限制重构子树。在这里我们可以发现,当阈值限制越紧[0.5,0.75],对修改少询问多有利;当阈值限定越松[0.75,1.0],对修改多查询少有利。
根据阈值,我们就可以对子树进行重构了。
但是问题又来了,在条根到叶子的链上可能有许许多多的“不平衡点”,我们要怎样确定最优的一个,实际上,我们只需要选择最靠近根节点的一个就好了,因为如果修改的层数太深,在总体上不会对树的结构产生多么大的影响,所以选最靠近根的一个相对来说比较优秀。
那么维护平衡树性质的部分我们解决了,但是插入以及删除呢?
首先,对于插入,直接参见动态开点大法,在向下插入的时候动态开点就好了(此处应该注意回收内存)
其次,对于删除,我们既不能把它转到叶子结点(treap),也不能直接裂开丢掉再合并(非旋转treap),所以我们给它打上一个real标记,real标记存在说明这个节点信息为’真‘,否则为‘假’(已经删除),然后在重构子树的时候直接丢掉然后回收内存就好了
其他操作rank,get_kth,pre,nxt之类的操作直接套用平衡树常见套路就好了
然后写数组版的朋友们要注意
需要维护父亲和儿子的关系
不然会莫名其妙地丢失子树
例题:BZOJ3224 Tyvj 1728 普通平衡树
Description
您需要写一种数据结构(可参考题目标题),来维护一些数,其中需要提供以下操作:
\1. 插入x数
\2. 删除x数(若有多个相同的数,因只删除一个)
\3. 查询x数的排名(若有多个相同的数,因输出最小的排名)
\4. 查询排名为x的数
\5. 求x的前驱(前驱定义为小于x,且最大的数)
\6. 求x的后继(后继定义为大于x,且最小的数)
Input
第一行为n,表示操作的个数,下面n行每行有两个数opt和x,opt表示操作的序号(1<=opt<=6)
Output
对于操作3,4,5,6每行输出一个数,表示对应答案
Sample Input
10
1 106465
4 1
1 317721
1 460929
1 644985
1 84185
1 89851
6 81968
1 492737
5 493598
Sample Output
106465
84185
492737
HINT
1.n的数据范围:n<=100000
2.每个数的数据范围:[-2e9,2e9]
上板子
//yangkai
#include<bits/stdc++.h>
using namespace std;
inline int read(){
int data=0,w=1;char ch=getchar();
while(!isdigit(ch)&&ch!='-')ch=getchar();
if(ch=='-')w=-1,ch=getchar();
while(isdigit(ch))data=(data<<1)+(data<<3)+(ch-'0'),ch=getchar();
return data*w;
}
struct SGT{
static const int N=100010;
static const double alpha=0.75;
//内存池
int st[N],top,tot;
int get_place(){return top?st[top--]:++tot;}
void del_place(int t){st[++top]=t;}
void clean_st(){top=tot=0;}
//SGT
int root;
int ls[N],rs[N],fa[N];
int val[N],siz[N],all[N];
bool real[N];
void pushup(int t){
siz[t]=siz[ls[t]]+siz[rs[t]]+real[t];
all[t]=all[ls[t]]+all[rs[t]]+1;
}
bool check(int t){//返回1说明需要重构
if(all[ls[t]]>=alpha*all[t])return 1;
if(all[rs[t]]>=alpha*all[t])return 1;
return 0;
}
int newnode(int vl=0,int f=0){
int t=get_place();
ls[t]=rs[t]=0;
val[t]=vl;
siz[t]=all[t]=1;
real[t]=1;
fa[t]=f;
return t;
}
//收集需要重构的下标
void collect(int t,vector<int> &v){
if(!t)return;
collect(ls[t],v);
if(real[t])v.push_back(t);
else del_place(t);
collect(rs[t],v);
}
//重构划分
int divide(int l,int r,vector<int> v){
if(l>=r)return 0;
int mid=(l+r)>>1;
int t=v[mid];
ls[t]=divide(l,mid,v);
rs[t]=divide(mid+1,r,v);
fa[ls[t]]=fa[rs[t]]=t;//维护父亲信息
pushup(t);
return t;
}
//重新建树
void rebuild(int &t){
static vector<int> v;v.clear();
int f=fa[t];
collect(t,v);
t=divide(0,v.size(),v);
fa[t]=f;//维护父亲信息
}
//查询vl的排名
int rank(int vl){
int t=root,ans=1;
while(t){
if(vl<=val[t])t=ls[t];
else{
ans+=siz[ls[t]]+real[t];
t=rs[t];
}
}
return ans;
}
//查询排名为k的数
int get_kth(int k){
int t=root;
while(t){
if(siz[ls[t]]+1==k&&real[t])return val[t];
if(siz[ls[t]]>=k)t=ls[t];
else k-=siz[ls[t]]+real[t],t=rs[t];
}
}
//插入vl值
int insert(int &t,int f,int vl){//返回深度最浅的不合法点
if(!t){t=newnode(vl,f);return 0;}
int res;
if(vl<=val[t])res=insert(ls[t],t,vl);
else res=insert(rs[t],t,vl);
pushup(t);
//检查是否需要重构
if(check(t))res=t;
return res;
}
void insert(int vl){
int t=insert(root,0,vl);
if(!t)return;
if(t==root)rebuild(root);
else{
int f=fa[t];
if(t==ls[f])rebuild(ls[f]);
else rebuild(rs[f]);
}
}
//删除排名为k的数
void erase(int t,int k){
siz[t]--;
if(real[t]&&k==siz[ls[t]]+real[t]){real[t]=0;return;}
if(k<=siz[ls[t]])erase(ls[t],k);
else erase(rs[t],k-siz[ls[t]]-real[t]);
}
//删除数vl
void erase(int vl){
erase(root,rank(vl));
if(siz[root]<alpha*all[root])rebuild(root);
}
//查询vl的前驱和后继
int pre(int vl){return get_kth(rank(vl)-1);}
int nxt(int vl){return get_kth(rank(vl+1));}
//输出调试
void out_put(int t){
cout<<t<<"||"<<endl;
if(ls[t])out_put(ls[t]);
if(real[t])cout<<val[t]<<" L:"<<val[ls[t]]<<" R:"<<val[rs[t]]<<endl;
if(rs[t])out_put(rs[t]);
}
}sgt;
int main(){
int n=read();
while(n--){
int op=read(),t=read();
switch(op){
case 1:sgt.insert(t);break;
case 2:sgt.erase(t);break;
case 3:printf("%d\n",sgt.rank(t));break;
case 4:printf("%d\n",sgt.get_kth(t));break;
case 5:printf("%d\n",sgt.pre(t));break;
case 6:printf("%d\n",sgt.nxt(t));break;
}
}
return 0;
}