P3369 【模板】普通平衡树

传送门

splay模板

优质讲解

我只是发一个模板...

我太弱了讲不清

注意操作时可能会访问到最小值的前驱或最大值的后继

所以要多加入两个虚节点INF和 -INF防止越界

#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
using namespace std;
const int N=1e5+7;
inline int read()
{
    register int x=0,f=1;
    char ch=getchar();
    while(ch<'0'||ch>'9')
    {
        if(ch=='-') f=-1;
        ch=getchar();
    }
    while(ch>='0'&&ch<='9')
    {
        x=(x<<1)+(x<<3)+(ch^48);
        ch=getchar();
    }
    return x*f;
}
int n;

//------------------------------Splay--------------------------------
int ch[N<<2][2],sz[N<<2],fa[N<<2],val[N<<2],num[N<<2],cnt,rt;
inline void pushup(int x){ sz[x]=sz[ch[x][0]]+sz[ch[x][1]]+num[x]; }//更新当前节点
inline void rotate(int x,int &k)//伸展
{
    int y=fa[x],z=fa[y],d=(ch[y][1]==x);
    if(y==k) k=x;
    else ch[z][(ch[z][1]==y)]=x;
    fa[x]=z; fa[y]=x; fa[ch[x][d^1]]=y; 
    ch[y][d]=ch[x][d^1]; ch[x][d^1]=y;
    pushup(y); pushup(x);
}
inline void splay(int x,int &k)//splay
{
    while(x!=k)
    {
        int y=fa[x],z=fa[y];
        if(y!=k)
        {
            if( (ch[y][1]==x)^(ch[z][1]==y) ) rotate(x,k);
            else rotate(y,k);
        }
        rotate(x,k);
    }
}
inline void find(int x)//找到数x,并将它弄到树根
{
    int now=rt;
    while(ch[now][ x>val[now] ] && x!=val[now]) now=ch[now][ x>val[now] ];
    splay(now,rt);
}
inline int Q_kth(int k)//询问第k大
{
    int now=rt;
    while(233)
    {
        if(ch[now][0]&&k<=sz[ch[now][0]]) { now=ch[now][0]; continue; }
        if(k>sz[ ch[now][0] ]+num[now] )
        {
            k-=sz[ ch[now][0] ]+num[now];
            now=ch[now][1];
            continue;
        }
        return now;
    }
}
inline void Q_rank(int x)//询问x的排名
{
    find(x);//把它转到树根,然后输出左子树的大小就好了(左边都比它小,右边都比它大)
    printf("%d
",sz[ch[rt][0]]/*注意不用加1,因为有多一个虚节点-INF在左边*/);
}
inline int pre(int x)//询问前驱,把节点转到根然后输出左子树中的最大值(即左子树最右边的节点)
{
    find(x);
    if(val[rt]<x) return rt;//特判一下
    int now=ch[rt][0];//左子树
    while(ch[now][1]) now=ch[now][1];//一直往右走
    return now;
}
inline int nex(int x)//同理
{
    find(x);
    if(val[rt]>x) return rt;
    int now=ch[rt][1];
    while(ch[now][0]) now=ch[now][0];
    return now;
}
inline void ins(int x)//插入节点
{
    int now=rt,f=0;
    while(now&&val[now]!=x)//先找到位置
    {
        f=now;
        now=ch[now][ x>val[now] ];
    }
    if(now) num[now]++,sz[now]++;//如果已经有值就直接更新
    else//否则就增加节点
    {
        now=++cnt;
        if(f) ch[f][ x>val[f] ]=now;
        val[now]=x; fa[now]=f;
        num[now]=sz[now]=1;
    }
    splay(now,rt);//最后要Splay一波来更新整颗树的状态
}
inline void erase(int x)//删除节点,把前驱转到根,后继转到根的右儿子,那么后继的左子树有且只有当前节点
{
    int now=nex(x);
    splay(pre(x),rt); splay(now,ch[rt][1]);
    now=ch[now][0];
    if(num[now]>1)//如果不止一个就减1
        num[now]--,sz[now]--;
    else ch[fa[now]][0]=0;//否则就删除节点
    pushup(fa[now]);
    pushup(fa[fa[now]]);
}
//------------------------------------------------------------

int main()
{
    n=read();
    ins(0x3f3f3f3f); ins(0xcfcfcfcf);//插入虚节点
    int a,b;
    for(int i=1;i<=n;i++)
    {
        a=read(); b=read();
        if(a==1) ins(b);
        if(a==2) erase(b);
        if(a==3) Q_rank(b);
        if(a==4) printf("%d
",val[ Q_kth(b+1/*注意+1,因为有虚节点*/) ]);
        if(a==5) printf("%d
",val[ pre(b) ]);
        if(a==6) printf("%d
",val[ nex(b) ]);
    }
    return 0;
}
奇怪的模板

$Upd in one year later $

上面那个链接真的只是优质讲解...模板就没那么优质了QAQ

经过了最近几天的艰苦奋斗我终于意识到了这个模板的局限性....

灵活性真的不行啊

上面的模板把重复的值合并到一个节点里面,使得代码要多注意很多恶心的细节

删除节点时因为可能会访问到边界外面所以要多插两个节点,使得细节更多...

而且对于二逼平衡树这一题,如果用线段树套 $Splay$,那么每个线段树的节点的splay都要多来两个节点,那么总节点就又要多 $2nlog_n$ 个

对于本来就十分卡线段树套$Splay$的这题来说更是难受,并且很容易因为细节问题出事,一开始用这个 $splay$ 写真的是要写疯掉了也写不出来,最后迫不得已只能重新学一份模板然后重构二逼平衡树的代码QAQ

所以这里放一个比较灵活方便的不合并重复节点的 $Splay$,真的好写很多

并且注意一下, $Splay$ 的复杂度理论上是均摊 $log_n$ 的

但是不代表它一直都是平衡的,所以对于每个复杂度为树高的操作最后都要 $splay$ 一波来保证复杂度

洛谷上这题只卡了求排名后不 $splay$ 的算法,其他操作很良心地没有卡

所以我也只有查排名才 $splay$ 2333

感觉平衡树也没什么好注释的了,反正大家都看得懂每个操作干什么2333

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<cstring>
using namespace std;
typedef long long ll;
inline int read()
{
    int x=0,f=1; char ch=getchar();
    while(ch<'0'||ch>'9') { if(ch=='-') f=-1; ch=getchar(); }
    while(ch>='0'&&ch<='9') { x=(x<<1)+(x<<3)+(ch^48); ch=getchar(); }
    return x*f;
}
const int N=2e5+7,INF=1e9+7;
int n,ans;
int rt,c[N][2],fa[N],sz[N],val[N],cnt;
inline void pushup(int x) { sz[x]=sz[c[x][0]]+sz[c[x][1]]+1; }
inline void rotate(int x,int &k)
{
    int y=fa[x],z=fa[y],d=(c[y][1]==x);
    y==k ? k=x : c[z][c[z][1]==y]=x;
    fa[x]=z; fa[y]=x; fa[c[x][d^1]]=y;
    c[y][d]=c[x][d^1]; c[x][d^1]=y;
    pushup(y); pushup(x);
}
inline void splay(int x,int &k)
{
    while(x!=k)
    {
        int y=fa[x],z=fa[y];
        if(y!=k)
        {
            if(c[y][0]==x ^ c[z][0]==y) rotate(x,k);
            else rotate(y,k);
        }
        rotate(x,k);
    }
}
inline void Q_rank(int k)
{
    int x=rt,res=0,f=0;
    while(x)
    {
        f=x;
        if(val[x]>=k) x=c[x][0];
        else res+=sz[c[x][0]]+1,x=c[x][1];
    }
    if(f) splay(f,rt);
    printf("%d
",res+1);
}
inline int Q_kth(int x,int k)
{
    while(233)
    {
        if(sz[c[x][0]]+1==k) return x;
        if(sz[c[x][0]]>=k) { x=c[x][0]; continue; }
        k-=sz[c[x][0]]+1; x=c[x][1];
    }//理论上此处要有splay
}
inline void ins(int k)
{
    int x=rt,f=0;
    while(x) f=x,x=c[x][k>val[x]];
    x=++cnt; fa[x]=f; val[x]=k; sz[x]=1;
    if(f) c[f][k>val[f]]=x;
    if(rt) splay(x,rt);
    else rt=x;
}
inline void del(int k)//删除就把节点转到根然后把左右子树直接并起来
{
    int x=rt; while(val[x]!=k) x=c[x][k>val[x]];
    splay(x,rt);
    if(c[x][0]&&c[x][1])
    {
        int y=Q_kth(rt,sz[c[x][0]]); splay(y,c[x][0]);
        x=rt; c[y][1]=c[x][1]; fa[c[x][1]]=y;
        rt=y; fa[rt]=0; pushup(y);
    }
    else if(c[x][0]) fa[c[x][0]]=0,rt=c[x][0];
    else if(c[x][1]) fa[c[x][1]]=0,rt=c[x][1];
    else rt=0;
}
void pre(int x,int k)//理论上前驱和后继跑完都要splay最底下的节点
{
    if(!x) return;
    if(ans<val[x]&&k>val[x]) ans=val[x];
    pre(c[x][k>val[x]],k);
}
void nex(int x,int k)
{
    if(!x) return;
    if(ans>val[x]&&k<val[x]) ans=val[x];
    nex(c[x][k>=val[x]],k);
}
int main()
{
    n=read();
    int a,b;
    while(n--)
    {
        a=read(),b=read();
        if(a==1) { ins(b); continue; }
        if(a==2) { del(b); continue; }
        if(a==3) { Q_rank(b); continue; }
        if(a==4) { printf("%d
",val[Q_kth(rt,b)]); continue; }
        if(a==5) { ans=-INF; pre(rt,b); printf("%d
",ans);  continue; }
        ans=INF; nex(rt,b); printf("%d
",ans);
    }
    return 0;
}
原文地址:https://www.cnblogs.com/LLTYYC/p/9770678.html