替罪羊树学习笔记

博客咕咕咕了好久……最近会逐步继续恢复更新博客的。
最近又在学习二叉搜索树。实测发现替罪羊树快的飞起(时间约Splay的1/2)~写起来还比较简单,决定来一波。
(那为什么还要用Splay呢?因为Splay是序列之王!还能维护LCT!(你要用非旋Treap(FHQ-Treap)我也没意见))
替罪羊树的主要思想就是当出现重量失衡的时候,把罪魁祸首的那个子树拎出来,重新按最完美的方式(也就是近似完全二叉树)构造一遍再接回去
如何定义某个子树不平衡:当这个子树的左右子树其中之一的“重量”(节点个数)超过了整个子树的α*100%时,我们认为这个子树不平衡。
举例:α=0.75时,如果一个子树左子树有4个节点,右子树有1个,这个子树大小就是4+1+1=6,左子树占比超过了α*100%(即75%),这个子树不平衡,需要重构。
显然,α取值介于0.5至1.0之间,越小树越平衡但重构次数越多,越大重构次数越少但树越不平衡。太大太小都会出事。一般而言,α取0.75。如果题目查询次数远大于插入次数,可略微降低α取值(比如α=0.70);若远小于,则略升高(如α=0.80)。
下面以洛谷3369【模板】普通平衡树为例:

//这个板子有改进之处:比如删除节点可以打上删除懒标记,单个节点可以记录同一数字数量避免多余节点。
#include
#include
#include
#define inf (1<<30)
#define maxn (2100000)
#define db double
#define il inline
#define RG register
using namespace std;

const db al=0.75;//α
struct node {
    int son[2],fa,size,num;//左右孩子储存地址,节点父亲,以该节点为根子树的重量,该节点储存的数字
} t[maxn];
int n,cnt,root;

il bool balance(RG int id)   //判断子树是否平衡
{
    return (db)(t[id].size*al>=(db)t[ t[id].son[0] ].size) && (db)( t[id].size*al>=(db)t[t[ id].son[1] ].size);
}
int cur[maxn],sum;

il void recycle(RG int id)    //压扁,把需要重构的子树拎出来先拍扁成序列
{
    if(t[id].son[0]) recycle(t[id].son[0]);
    cur[++sum]=id;
    if(t[id].son[1]) recycle(t[id].son[1]);
}

il int build(RG int l,RG int r)   //递归建树,使结构最优
{
    if(l>r) return 0;
    RG int mid=(l+r)>>1,id=cur[mid];
    t[ t[id].son[0]=build(l,mid-1) ].fa=id;
    t[ t[id].son[1]=build(mid+1,r) ].fa=id;
    t[id].size=t[ t[id].son[0] ].size+t[ t[id].son[1] ].size+1;
    return id;
}

il void rebuild(RG int id)   //重构子树,再“接回去”
{
    sum=0;
    recycle(id);
    RG int fa=t[id].fa,Son=( t[ t[id].fa ].son[1]==id );
    RG int cur=build(1,sum);
    t[ t[fa].son[Son]=cur ].fa=fa;
    if(id==root) root=cur;
}

il void insert(RG int x)//插入一个数字x
{
    RG int now=root,cur=++cnt;
    t[cur].size=1,t[cur].num=x;
    while(1) { //找到适合位置插入
        t[now].size++;
        RG bool Son=(x>=t[now].num);
        if( t[now].son[Son] ) now=t[now].son[Son];
        else {
            t[ t[now].son[Son]=cur ].fa=now;
            break;
        }
    }
    RG int flag=0;
    for(RG int i=cur; i; i=t[i].fa) if(!balance(i)) flag=i;//注意:重建时取深度最浅的,以避免小子树重构完大子树还重构,浪费时间
    if(flag) rebuild(flag); //插入往往会导致不平衡,这时需要重建不平衡的子树即可
}

il int get_num(RG int x)   //查询 x 在树中的节点编号(在数组中储存位置下标)
{
    RG int now=root;
    while(1) {
        if(t[now].num==x) return now;
        else now=t[now].son[ t[now].num=x) now=t[now].son[0];
        else x-=t[ t[now].son[0] ].size+1,now=t[now].son[1];
    }
    return now;
}
int main()
{
    cnt=2,root=1;
    t[1].num=-inf,t[1].size=2,t[1].son[1]=2;
    t[2].num=inf,t[2].size=1,t[2].fa=1;
    scanf("%d",&n);
    RG int type,x;
    for(RG int i=1; i<=n; i++) {
        scanf("%d%d",&type,&x);
        if(type==1) insert(x);
        else if(type==2) erase( get_num(x) );
        else if(type==3) printf("%d\n",get_rank(x));
        else if(type==4) printf("%d\n",t[ get_kth(x+1) ].num);
        else if(type==5) printf("%d\n",t[get_kth(get_rank(x))].num);
        else if(type==6) printf("%d\n",t[get_kth(get_rank(x+1)+1)].num);//注意此处 
    }
    return 0;
}

你可能感兴趣的:(替罪羊树学习笔记)