SPLAY 入门

@(splay入门详解)

引入

splay是一种BST,可以维护静态区间k小
也可以当作区间树,维护区间信息( 求和,最大子段和,翻转区间,等等)
时间一般O(nlogn)(均摊),splay操作满足其稳定性
但是常数巨大,接近100,慎用

splay的构建

一个完整的splay包含以下变量:

  1. fa:此节点的父亲
  2. val:此点的权值
  3. cnt:此权值的数量(区间树中则不需要)
  4. ch[2]:此点的儿子
  5. sz:它以及儿子的大小
  6. tag:lazytag(如果有需要)

接下来是一些操作

基础操作

建树

可以一个一个insert,也可以直接构造满二叉树

int build(int l,int r,int f){
    if(l>r) return 0;
    int mid=((l+r)>>1),x;
    if(top) x=rb[top--];
    else x=++tot;
    t[x].fa=f;
    t[x].val=a[mid];
    t[x].rev=t[x].laz=0;
    t[x].ls=t[x].rs=Max(a[mid],0);
    t[x].ch[0]=build(l,mid-1,x);
    t[x].ch[1]=build(mid+1,r,x);
    push_up(x);
    return x;
}

get

获取此点在父亲那里的位置

int get(int x){
    return (t[t[x].fa].ch[1]==x);
}

rotate

splay基础操作,把两个点旋转之后却不能破坏BST条件
有一个规律:此点的在(父亲在祖父那里的位置)的位置的儿子不会变

void rotate(int x){
    int f=t[x].fa;int g=t[f].fa;
    int kx=get(x),kf=get(f); 
    t[t[x].ch[kx^1]].fa=f;
     t[f].ch[kx]=t[x].ch[kx^1];//不满足条件的儿子转到原来此点在父亲那里的地方
    t[f].fa=x;
     t[x].ch[kx^1]=f;//父亲变成此点的儿子
    t[x].fa=g;
     t[g].ch[kf]=x;//此点变成祖父的儿子
    push_up(f);
    push_up(x);
} 

splay

核心操作,把一个点旋转到根,确保平衡性,在修改操作之后
一般用双旋满足平衡性
对于不同父亲和儿子的相对位置,有以下两种情况:

aHR0cHM6Ly9jZG4ubHVvZ3Uub3JnL3VwbG9hZC9waWMvNjE4MjgucG5n

第一种先转父亲,第二种先转儿子

void splay(int x,int v){
    while(t[x].fa!=v){
        if(t[t[x].fa].fa!=v){ //注意别转多了
            rotate(get(x)==get(t[x].fa)?t[x].fa:x);// 如果get(x)==get(fa) 就旋转父亲,否则旋转儿子
        }
        rotate(x);//第二下一定是旋转x
    }
    if(!v) rt=x;//换根
}

insert

我们要在pos处加入len个数,插入之后还要满足平衡树平衡(中序遍历要严格单调)的条件,应该怎么插入呢?
把pos splay到根,再把pos+1 splay到根的右儿子处,那么pos+1的点的左儿子就一定是pos到pos+1之间的数,也就是空的
那么在这时把要插入的区间构成一颗满二叉树,再把根和pos+1连接,就完成了插入
插入完成之后记得push_up
要更新信息

void insert(int l,int len){
    int r=l+1;
    l=kth(l+1);r=kth(r+1);
    splay(l,0);splay(r,l);//提取区间
    for(int i=1;i<=len;i++){
        a[i]=read();
    }
    t[r].ch[0]=build(1,len,r);
    n+=len;
    push_up(r);push_up(l);//先pushr,因为在下面
}

kth

重点操作,查询第k大,用splayBST的性质搜索即可
查询到一个点时,可分为几个区间
① k <= 左子树size,直接递归左子树
② 左子树size < k <= 左size+cnt[now],当前值!
⑨ k > 左size+cnt[now] 先减去左size+cnt[now],再递归右子树
区间k大的话可以先提取区间!

int kth(int k){
    int x=rt;
    while(1){
        push_down(x);
        if(k<=t[t[x].ch[0]].sz){
            x=t[x].ch[0];
        }else if(k==t[t[x].ch[0]].sz+1){
            return x;
        }else{
            k-=t[t[x].ch[0]].sz+1;
            x=t[x].ch[1];
        }
    }
}

delete

删除此点,提取区间后断开联系就可以,注意有多个的情况要--cnt,不断联系
区间树:

void recycle(int x){
    if(!x) return;
    rb[++top]=x;
    recycle(t[x].ch[0]);
    recycle(t[x].ch[1]);
} 
void del(int l,int r){
    n-=r-l+1;
    l=kth(l);r=kth(r+2);
    splay(l,0);splay(r,l);
    recycle(t[r].ch[0]);//recycle是内存回收
    t[r].ch[0]=0;
    push_up(r);
    push_up(l);
}

权值BST:

inline void del(long long key){
    long long pr=pre(key),su=suc(key);
    splay(pr,0);
    splay(su,pr);
    long long u=ch[su][0];
    //把key的前驱伸展到rt,后继伸展到rt的右儿子
    //那么key一定是后继的左儿子,而且没有儿子
    if(cnt[u]>1){
        cnt[u]--;
        splay(u,0);
    }else{
        ch[su][0]=0;
        splay(su,0);
        cnt[u]=0;
    } 
}

find

把距这个值最近的值splay到根

inline void find(long long key){
    long long u=rt;
    while(val[u]!=key&&ch[u][key>val[u]]) u=ch[u][key>val[u]];
    splay(u,0);
}

pre

求前驱,先find
然后检查这个值在它前面还是在后面
在前面一定是前驱
在后面的话,一定是根左子树中最大的

inline long long pre(long long key){
    find(key);
    if(val[rt]<key) return rt;
    long long u=ch[rt][0];
    while(ch[u][1]){
        u=ch[u][1];
    }
    return u;
} 

suc

求后继,同pre

inline long long suc(long long key){
    find(key);
    if(val[rt]>key) return rt;
    long long u=ch[rt][1];
    while(ch[u][0]){
        u=ch[u][0];
    }
    return u;
}

基础操作就先这么多了,之后再更新进阶操作

注意事项

  1. 要多splay
  2. delete讨论cnt>1?
  3. insert更新信息,要找是否已有此节点
  4. rotate注意顺序和方向,要push_up

    splay应用

    基础运用:luogup3369普通平衡树

#include<iostream>
#include<cstdio>
using namespace std;
long long read(){
    char ch=getchar();long long x=0,pos=1;
    for(;!isdigit(ch);ch=getchar()) if(ch=='-') pos=0;
    for(;isdigit(ch);ch=getchar()) x=x*10+ch-'0';
    return pos?x:-x;
}
const long long maxn=1000111;
long long n,fa[maxn],val[maxn],cnt[maxn],ch[maxn][3],sz[maxn],rt,tot;
inline long long get(long long u){
    return (ch[fa[u]][1]==u);
}
inline void push_up(long long u){
    sz[u]=sz[ch[u][0]]+sz[ch[u][1]]+cnt[u];
}
inline void rotate(long long u){
    long long f=fa[u];long long g=fa[f];long long k=get(u);
    fa[u]=g;
     ch[g][get(f)]=u;
    fa[ch[u][k^1]]=f;
     ch[f][k]=ch[u][k^1];
    fa[f]=u;
     ch[u][k^1]=f;
     
    push_up(f);
    push_up(u); // ※※※ 
}
inline void splay(long long u,long long v){
    while(fa[u]!=v){
        long long f=fa[u];
        if(fa[f]!=v){
            rotate((get(f)==get(u)?f:u));
        }
        rotate(u);
    }
    if(!v) rt=u;
}
inline void insert(long long key){
    long long u=rt,p=0;
    while(u&&val[u]!=key){
        p=u;
        u=ch[u][key>val[u]];
    }
    if(u) ++cnt[u];
    else{
        u=++tot; 
        val[u]=key;
        if(p){
            ch[p][key>val[p]]=u;
        }
        sz[u]=1;
        fa[u]=p;
        cnt[u]=1;
        ch[u][0]=ch[u][1]=0;
    } 
    splay(u,0);
}
inline void find(long long key){
    long long u=rt;
    while(val[u]!=key&&ch[u][key>val[u]]) u=ch[u][key>val[u]];
    splay(u,0);
}
inline long long rnk(long long key){
    find(key);
    return sz[ch[rt][0]];//有-inf这个点,不用加一 
}
inline long long kth(long long k){
    ++k;// -inf
    long long u=rt;
    while(1926){
        if(sz[ch[u][0]]+cnt[u]<k) k-=(sz[ch[u][0]]+cnt[u]),u=ch[u][1];
        else if(k<=sz[ch[u][0]]) u=ch[u][0];
        else return val[u]; 
    }
    //k < sz[ch[u][0]]: 在左子树中
    //sz[ch[u][0]] < k <= sz[ch[u][0]]+cnt[u] 为当前值
    //否则是右子树中 
}
inline long long pre(long long key){
    find(key);
    if(val[rt]<key) return rt;
    long long u=ch[rt][0];
    while(ch[u][1]){
        u=ch[u][1];
    }
    return u;
} 
inline long long suc(long long key){
    find(key);
    if(val[rt]>key) return rt;
    long long u=ch[rt][1];
    while(ch[u][0]){
        u=ch[u][0];
    }
    return u;
}
inline void del(long long key){
    long long pr=pre(key),su=suc(key);
    splay(pr,0);
    splay(su,pr);
    long long u=ch[su][0];
    //把key的前驱伸展到rt,后继伸展到rt的右儿子
    //那么key一定是后继的左儿子,而且没有儿子
    if(cnt[u]>1){
        cnt[u]--;
        splay(u,0);
    }else{
        ch[su][0]=0;
        splay(su,0);
        cnt[u]=0;
    } 
}
int main(){
    n=read();
    insert(-192608170);
    insert(192608170);
    long long x,opt;
    for(long long i=1;i<=n;i++){
        opt=read();x=read();
        if(opt==1){
            insert(x);
            continue;
        }
        if(opt==2){
            del(x);
            continue;
        }
        if(opt==3){
            printf("%lld\n",rnk(x));
            continue;
        }
        if(opt==4){
            printf("%lld\n",kth(x));
            continue;
        }
        if(opt==5){
            printf("%lld\n",val[pre(x)]);
            continue;
        }
        if(opt==6){
            printf("%lld\n",val[suc(x)]);
            continue;
        }
    }
    return 0;
}

转载于:https://www.cnblogs.com/lcyfrog/articles/11257134.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值