标签:cnt return int rbu 替罪羊 mid rc
因为之前学东西学得太菜所以重学了。
不说多了直接正题。
思想解析
本质上就是普通的BST,只是多了一个重建不平衡子树的重构操作。
很显然每个平衡树都有一个维护平衡的操作,那么替罪羊树的平衡操作就是重构。
最开始的时候教练说替罪羊十分暴力,一不平衡整棵树拍扁重构。但是其实并不是整棵树重构,而且操作并不算特别暴力,我个人觉得挺妙的,而且速度比平衡树双子(treap,splay)快。
好那么快速开始。
操作讲解
重构(rbu)
核心操作!开始。
我们对于一个不平衡的子树进行重构,很显然如果我们随随便便就重构是极其低能的,我们要对不平衡进行定义。于是有 \(canrbu\) 函数。我们设定一个平衡因子 \(alpha\) ,对于一颗子树,如果它的大小乘上 \(alpha\) 小于等于左右儿子的子树大小的最大值(这里约定一下,本文的子树大小为树上节点大小,实际大小为树上数的大小),或者当前子树的实际大小比子树大小乘 \(alpha\) 小了,很显然是不平衡的,于是进行重构。
Code:
inline bool canrbu(int x){
return t[x].cnt&&(t[x].sz*alpha<=(double)Max(t[lc(x)].sz,t[rc(x)].sz)||(double)t[x].fc<=t[x].sz*alpha);
}
确定是否需要重构后考虑如何重构。我们可以按中序遍历把子树拍扁,这样得到的序列满足二叉搜索树的性质,然后二分把树再提起来,这样树高就变成 \(log\) 的了。需要注意替罪羊的删除操作是惰性删除,只是将计数器减一,所以在重构的时候我们考虑只将计数器不为0的点拍扁,为0就扔掉不要了。
Code:
void rbu_ldr(int &top,int x){
if(!x) return;
rbu_ldr(top,lc(x));
if(t[x].cnt) fd[top++]=x;
rbu_ldr(top,rc(x));
}
int rbu_build(int l,int r){
if(l>=r) return 0;
int mid=(l+r)>>1;
lc(fd[mid])=rbu_build(l,mid);
rc(fd[mid])=rbu_build(mid+1,r);
update(fd[mid]);
return fd[mid];
}
inline void rbu(int &x){
int top=0;
rbu_ldr(top,x);
x=rbu_build(0,top);
}
插入,删除(ins,del)
直接按照二叉搜索树插入即可,只是需要注意,我们每一次操作完需要对子树进行检查,判断子树是否平衡,然后对于有需要的子树重建。
Code:
void ins(int &x,int k){
if(!x){
x=++tot;
if(!rt) rt=1;
lc(x)=rc(x)=0;
t[x].val=k;
t[x].cnt=t[x].fc=t[x].sz=1;
return;
}
if(k==t[x].val) ++t[x].cnt;
else if(k<t[x].val) ins(lc(x),k);
else if(k>t[x].val) ins(rc(x),k);
update(x);
if(canrbu(x)) rbu(x);
}
void del(int &x,int k){
if(!x) return;
if(k==t[x].val){
if(t[x].cnt) --t[x].cnt;
}
else if(k<t[x].val) del(lc(x),k);
else if(k>t[x].val) del(rc(x),k);
update(x);
if(canrbu(x)) rbu(x);
}
查找第一个小于/大于的数的排名(up_bound,up_great)
小进左,大进右即可。进右子树时把左子树的贡献加上。访问到空节点和找到目标时考虑对于需要进行调整即可。
Code:
int up_great(int x,int k){
if(!x) return 0;
if(k==t[x].val&&t[x].cnt) return t[lc(x)].fc;
else if(k<t[x].val) return up_great(lc(x),k);
else return up_great(rc(x),k)+t[lc(x)].fc+t[x].cnt;
}
int up_bound(int x,int k){
if(!x) return 1;
if(k==t[x].val&&t[x].cnt) return t[lc(x)].fc+t[x].cnt+1;
else if(k<t[x].val) return up_bound(lc(x),k);
else return up_bound(rc(x),k)+t[lc(x)].fc+t[x].cnt;
}
查询排名位k的数(kth)
经典思想,学过权值线段树或者其他平衡树的话不难理解。
Code:
int kth(int x,int k){
if(!x) return 0;
if(t[lc(x)].fc<k&&k<=t[lc(x)].fc+t[x].cnt) return t[x].val;
else if(t[lc(x)].fc+t[x].cnt<k) return kth(rc(x),k-t[lc(x)].fc-t[x].cnt);
else return kth(lc(x),k);
}
查询数的排名以及前驱、后继(rk,pre,nxt)
直接用前面的函数就好了。
inline int rk(int x,int k){
return up_great(x,k)+1;
}
inline int pre(int x,int k){
return kth(x,up_great(x,k));
}
inline int nxt(int x,int k){
return kth(x,up_bound(x,k));
}
喜闻乐见的嫁接
由于替罪羊树本质就是个BST,可以偷别的平衡树的操作过来,那么就可以支持序列维护啦(显然非常舒服)!
既然非旋就非旋到底,我们嫁接 \(fhq\ treap\) 的分裂合并过来。要用就直接用就可以了。
结构体封装时间
struct Scapegoat{
#define lc(x) t[x].lc
#define rc(x) t[x].rc
int tot,rt;
double alpha;
int fd[N];
Scapegoat(){
tot=rt=0;
alpha=0.7;
}
struct Node{
int lc,rc,cnt;
int val,fc,sz;
}t[N];
inline void update(int x){
t[x].sz=(t[lc(x)].sz+t[rc(x)].sz+1);
t[x].fc=(t[lc(x)].fc+t[rc(x)].fc+t[x].cnt);
}
inline bool canrbu(int x){
return t[x].cnt&&(t[x].sz*alpha<=(double)Max(t[lc(x)].sz,t[rc(x)].sz)||(double)t[x].fc<=t[x].sz*alpha);
}
void rbu_ldr(int &top,int x){
if(!x) return;
rbu_ldr(top,lc(x));
if(t[x].cnt) fd[top++]=x;
rbu_ldr(top,rc(x));
}
int rbu_build(int l,int r){
if(l>=r) return 0;
int mid=(l+r)>>1;
lc(fd[mid])=rbu_build(l,mid);
rc(fd[mid])=rbu_build(mid+1,r);
update(fd[mid]);
return fd[mid];
}
inline void rbu(int &x){
int top=0;
rbu_ldr(top,x);
x=rbu_build(0,top);
}
void ins(int &x,int k){
if(!x){
x=++tot;
if(!rt) rt=1;
lc(x)=rc(x)=0;
t[x].val=k;
t[x].cnt=t[x].fc=t[x].sz=1;
return;
}
if(k==t[x].val) ++t[x].cnt;
else if(k<t[x].val) ins(lc(x),k);
else if(k>t[x].val) ins(rc(x),k);
update(x);
if(canrbu(x)) rbu(x);
}
void del(int &x,int k){
if(!x) return;
if(k==t[x].val){
if(t[x].cnt) --t[x].cnt;
}
else if(k<t[x].val) del(lc(x),k);
else if(k>t[x].val) del(rc(x),k);
update(x);
if(canrbu(x)) rbu(x);
}
int up_great(int x,int k){
if(!x) return 0;
if(k==t[x].val&&t[x].cnt) return t[lc(x)].fc;
else if(k<t[x].val) return up_great(lc(x),k);
else return up_great(rc(x),k)+t[lc(x)].fc+t[x].cnt;
}
int up_bound(int x,int k){
if(!x) return 1;
if(k==t[x].val&&t[x].cnt) return t[lc(x)].fc+t[x].cnt+1;
else if(k<t[x].val) return up_bound(lc(x),k);
else return up_bound(rc(x),k)+t[lc(x)].fc+t[x].cnt;
}
int kth(int x,int k){
if(!x) return 0;
if(t[lc(x)].fc<k&&k<=t[lc(x)].fc+t[x].cnt) return t[x].val;
else if(t[lc(x)].fc+t[x].cnt<k) return kth(rc(x),k-t[lc(x)].fc-t[x].cnt);
else return kth(lc(x),k);
}
inline int rk(int x,int k){
return up_great(x,k)+1;
}
inline int pre(int x,int k){
return kth(x,up_great(x,k));
}
inline int nxt(int x,int k){
return kth(x,up_bound(x,k));
}
};
标签:cnt,return,int,rbu,替罪羊,mid,rc 来源: https://www.cnblogs.com/cbdsopa/p/15930218.html
本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享; 2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关; 3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关; 4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除; 5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。