Splay学习笔记

写在前面

蒟蒻现在才开始学splay[我真的好菜呀]

这是一篇有关splay入门学习的笔记qwq

就让splay开始吧?

首先要学习splay之前,要先知道splay是个什么玩意儿……

我们都知道二叉查找树的性质:左小右大 所以我们遍历二叉查找树的中序遍历是一个从小到大的序列qwq

二叉查找树的操作的时间复杂度是O(h)的

但是当出题人非常不良心[误]数据比较大的时候 直接使用二叉查找树显然会T……

因为当你的树被建成这样的时候,就gg了……

所以我们要引入平衡树的概念……

其实所谓的平衡树就是更加“平衡”的二叉搜索树

那么我们要怎么让这棵树更加“平衡”呢?

旋转这棵树上的节点!

rotate操作

rotate操作是splay的灵魂啊

我们考虑把一个点旋转到它的父亲会发生什么……

如果这个节点是它父亲的右儿子,那么把它旋到父亲之后,它的父亲就没有右儿子了

那么怎么办呢……

再想想,这个时候我们把它旋转到了父亲,原本的父亲就是它的左儿子啦[因为它比父亲大]

这个时候它的左儿子要往哪放呢……

这时候我们想起来父亲的右儿子没了……

对!就把它放到父亲的右儿子

这棵树仍然保持了二叉搜索树的性质

因为本来儿子节点的左子树小于儿子节点,但是大于父亲节点,所以把它塞到柚子树右子树是没有错的

然后这棵二叉搜索树的性质就是没错的啦x

图大概这样……假设我们要把A旋到B 

 第二幅图是旋转之后的样子 左旋和右旋同理

接下来稍微看一下代码吧……

void rotate(int x)
{
    int fa1=fa[x];
    int fa2=fa[fa1];
    int K=Get(x);
    int K1=Get(fa1);
    Son[fa1][K]=Son[x][K^1];
    fa[Son[fa1][K]]=fa1;
    Son[x][K^1]=fa1;
    fa[fa1]=x;
    fa[x]=fa2;
    if (fa2) Son[fa2][Son[fa2][1]==fa1]=x;
    Pushup(fa1);
    Pushup(x);
}

Pushup函数是更新这个节点的值 具体看代码

void Pushup(int x)
{
    if (x)
    {
        Size[x]=cnt[x];
        if (Son[x][0]) Size[x]+=Size[Son[x][0]];
        if (Son[x][1]) Size[x]+=Size[Son[x][1]];
    }
}

Get函数是得到左右儿子

bool Get(int Now)
{
    return Son[fa[Now]][1]==Now;
}

note:记得祖孙三代的值都要更新

cnt存在是因为二叉搜索树上不能有重复的数,于是我们就……在遇到同样的数的时候把cnt++!

好的 灵魂操作解决了接下来就相对轻松啦!

splay操作

splay操作的实质其实就是把一个节点一路旋转到目标节点

唯一需要注意的点是如果出现祖孙三代三点同在一条线上的时候,先旋转父亲再旋转自己,否则旋转两次自己

代码:

void Splay(int Now,int goal)
{
    for (int Fa;(Fa=fa[Now])!=goal;rotate(Now))
    if (fa[Fa]!=goal)
        if (Get(Now)==Get(Fa))
        rotate(Fa);
        else rotate(Now);
    if (!goal) rt=Now;
}

!goal的时候表示Now这个节点旋到根节点去了……

insert操作

insert操作的时候需要分类讨论一下

如果这是这棵树里面唯一的节点,这个点就是根

如果这个值已经在树里出现过了,那么就把它的cnt++

否则就和二叉搜索树一样把它插入到相应的位置就OK了

代码:

void Insert(int Now)
{
    if (rt==0)
    {
        size++;
        key[size]=Now;
        rt=size;
        cnt[size]=Size[size]=1;
        fa[size]=Son[size][0]=Son[size][1]=0;
        return;
    }
    int now=rt,Fa=0;
    while (1)
    {
        if (Now==key[now])
        {
            cnt[now]++;
            Pushup(now);Pushup(Fa);
            Splay(now);
            return;
        }
    Fa=now;now=Son[now][key[now]<Now];
    if (now==0)
        {
            size++;
            Size[size]=cnt[size]=1;
            Son[size][0]=Son[size][1]=0;
            Son[Fa][Now>key[Fa]]=size;
            fa[size]=Fa;
            key[size]=Now;
            Pushup(Fa);
            Splay(size);
            return;
        }
    }
}

动态求解K大和求解K在树中的排名

这两个操作是有点对称的……

有点像权值线段树的操作,如果左边够就往左边走,否则就把左边的扣掉往右边走

具体直接看代码应该挺好理解的?

K大:

int Kth(int Now)
{
    int now=rt;
    while (1)
    {
        if (Son[now][0]&&Now<=Size[Son[now][0]])
            now=Son[now][0];
        else
        {
            int temp=Size[Son[now][0]]+cnt[now];
            if (Now<=temp)
                return key[now];
            Now-=temp;
            now=Son[now][1];
        }
    }
}

rank:

int Rank(int Now)
{
    int now=rt,ans=0;
    while (1)
    {
        if (Now<key[now])
            now=Son[now][0];
        else
        {
            ans+=Size[Son[now][0]];
            if (Now==key[now])
            {
                Splay(now);
                return ans+1;
            }
        ans+=cnt[now];
        now=Son[now][1];
        }
    }
}

求解前驱和后继

因为splay完之后这个点的已经在根上了,它的前驱就是这个左子树中最右边的那个,后继就是柚子树右子树的最左边那个orz

pre:

int Pre()
{
    int now=Son[rt][0];
    while (Son[now][1]) now=Son[now][1];
    return now;
}

在求解前驱之前记得先把这个点插入进树里 顺便把他旋转到根

if (opt==5)
        {
            Insert(x);
            printf("%d
",key[Pre()]);
            del(x);
        }

nex:

int nex()
{
    int now=Son[rt][1];
    while (Son[now][0]) now=Son[now][0];
    return now;
}

同理在主程序中的处理:

if (opt==6)
        {
            Insert(x);
            printf("%d
",key[nex()]);
            del(x);
        }

delete操作

这是个比较麻烦的操作,所以我会尽量讲的清楚点orz

和insert一样,我们还是考虑分类讨论

先把要被删除的节点旋到根

如果树中被删除的值不止一个 直接把cnt--就完事了

如果被删除的数是树中唯一的值,把它删掉,树置为空

如果被删除的树只有左子树或者右子树,把它删了把左子树/右子树换成根就完事了

如果被删除的树同时有左右子树就先把它删了,把左子树旋到根上,然后把原本的右子树扔个新的根节点

然后分这四种情况讨论就OK啦

代码:

void del(int Now)
{
    Rank(Now);
    if (cnt[rt]>1)
    {
        cnt[rt]--;
        Pushup(rt);
        return;
    }
    if (!Son[rt][0]&&!Son[rt][1])
    {
        Clear(rt);rt=0;return;
    }
    if (!Son[rt][0])
    {
        int Ort=rt;
        rt=Son[rt][1];
        fa[rt]=0;
        Clear(Ort);
        return;
    }
    else if (!Son[rt][1])
    {
        int Ort=rt;
        rt=Son[rt][0];
        fa[rt]=0;
        Clear(Ort);
        return;
    }
    int Ort=rt;
    int Lb=Pre();
    Splay(Lb);
    Son[rt][1]=Son[Ort][1];
    fa[Son[Ort][1]]=rt;
    Clear(Ort);
    Pushup(Ort);
}

code[写的很丑]

#include <bits/stdc++.h>
using namespace std;
int Size[1000005],size;
int Son[1000005][2],cnt[1000005],fa[1000005],rt,key[1000005];
void Clear(int x)
{
    fa[x]=0;
    cnt[x]=0;
    key[x]=0;
    Son[x][0]=Son[x][1]=0;
    Size[x]=0;
}
void Pushup(int x)
{
    if (x)
    {
        Size[x]=cnt[x];
        if (Son[x][0]) Size[x]+=Size[Son[x][0]];
        if (Son[x][1]) Size[x]+=Size[Son[x][1]];
    }
}
bool Get(int Now)
{
    return Son[fa[Now]][1]==Now;
}
void rotate(int x)
{
    int fa1=fa[x];
    int fa2=fa[fa1];
    int K=Get(x);
    int K1=Get(fa1);
    Son[fa1][K]=Son[x][K^1];
    fa[Son[fa1][K]]=fa1;
    Son[x][K^1]=fa1;
    fa[fa1]=x;
    fa[x]=fa2;
    if (fa2) Son[fa2][Son[fa2][1]==fa1]=x;
    Pushup(fa1);
    Pushup(x);
}
void Splay(int Now)
{
    for (int Fa;Fa=fa[Now];rotate(Now))
    if (fa[Fa])
        if (Get(Now)==Get(Fa))
        rotate(Fa);
        else rotate(Now);
    rt=Now;
}
void Insert(int Now)
{
    if (rt==0)
    {
        size++;
        key[size]=Now;
        rt=size;
        cnt[size]=Size[size]=1;
        fa[size]=Son[size][0]=Son[size][1]=0;
        return;
    }
    int now=rt,Fa=0;
    while (1)
    {
        if (Now==key[now])
        {
            cnt[now]++;
            Pushup(now);Pushup(Fa);
            Splay(now);
            return;
        }
    Fa=now;now=Son[now][key[now]<Now];
    if (now==0)
        {
            size++;
            Size[size]=cnt[size]=1;
            Son[size][0]=Son[size][1]=0;
            Son[Fa][Now>key[Fa]]=size;
            fa[size]=Fa;
            key[size]=Now;
            Pushup(Fa);
            Splay(size);
            return;
        }
    }
}
int Rank(int Now)
{
    int now=rt,ans=0;
    while (1)
    {
        if (Now<key[now])
            now=Son[now][0];
        else
        {
            ans+=Size[Son[now][0]];
            if (Now==key[now])
            {
                Splay(now);
                return ans+1;
            }
        ans+=cnt[now];
        now=Son[now][1];
        }
    }
}
int Kth(int Now)
{
    int now=rt;
    while (1)
    {
        if (Son[now][0]&&Now<=Size[Son[now][0]])
            now=Son[now][0];
        else
        {
            int temp=Size[Son[now][0]]+cnt[now];
            if (Now<=temp)
                return key[now];
            Now-=temp;
            now=Son[now][1];
        }
    }
}
int Pre()
{
    int now=Son[rt][0];
    while (Son[now][1]) now=Son[now][1];
    return now;
}
int nex()
{
    int now=Son[rt][1];
    while (Son[now][0]) now=Son[now][0];
    return now;
}
void del(int Now)
{
    Rank(Now);
    if (cnt[rt]>1)
    {
        cnt[rt]--;
        Pushup(rt);
        return;
    }
    if (!Son[rt][0]&&!Son[rt][1])
    {
        Clear(rt);rt=0;return;
    }
    if (!Son[rt][0])
    {
        int Ort=rt;
        rt=Son[rt][1];
        fa[rt]=0;
        Clear(Ort);
        return;
    }
    else if (!Son[rt][1])
    {
        int Ort=rt;
        rt=Son[rt][0];
        fa[rt]=0;
        Clear(Ort);
        return;
    }
    int Ort=rt;
    int Lb=Pre();
    Splay(Lb);
    Son[rt][1]=Son[Ort][1];
    fa[Son[Ort][1]]=rt;
    Clear(Ort);
    Pushup(Ort);
}
int main()
{
    int N;
    scanf("%d",&N);
    while (N--)
    {
        int opt,x;
        scanf("%d%d",&opt,&x);
        if (opt==1)    
            Insert(x);
        if (opt==2)
            del(x);
        if (opt==3)
            printf("%d
",Rank(x));
        if (opt==4)
            printf("%d
",Kth(x));
        if (opt==5)
        {
            Insert(x);
            printf("%d
",key[Pre()]);
            del(x);
        }
        if (opt==6)
        {
            Insert(x);
            printf("%d
",key[nex()]);
            del(x);
        }
    }
    return 0;
}

题目是luogu P3369,只涉及到最基本的平衡树操作

区间翻转和lazy tag,下传tag

 我们要翻转一个序列上的[l,r]区间,在splay上要怎么做呢?

考虑以区间的下标建立起splay,无论怎么变动,中序遍历的区间下标是不变的qwq

然后考虑交换splay每个点上的数字

如果暴力修改是O(N)[大概?]的效率

我们需要更加优秀的修改方式

想想线段树怎么处理这种问题的?

lazy tag呀!

为了方便我们多加入两个虚拟节点-inf 和 inf

要修改的区间就是[l+1,r+1]

但是这些区间不在同一个子树下 tag打不上可咋整啊?

那就把他们旋转到同一个子树下!

把r+2旋转到根

再把l旋到r+2的左儿子 这时候[l+1,r+1]就会在根节点的右子树的左子树下了

可以稍微画图理解一下……

然后对于这玩意儿打上tag就行了

然后insert数据的时候还有一个小trick

如果一个个插入未免太慢了,我们考虑类似于线段树的build去建立一棵splay

具体实现细节可以看代码qwq

#include <bits/stdc++.h>
using namespace std;
int Size[1000005],size;
int Son[1000005][2],cnt[1000005],data[1000005],fa[1000005],rt,key[1000005],tag[10000005];
void Pushup(int x)
{
        Size[x]=1;
        Size[x]+=Size[Son[x][0]];
        Size[x]+=Size[Son[x][1]];
}
void PushDown(int x)
{
    if (x&&tag[x])
        {
            tag[Son[x][0]]^=1;
            tag[Son[x][1]]^=1;
            swap(Son[x][0],Son[x][1]);
            tag[x]=0;
        }
}
bool Get(int Now)
{
    return Son[fa[Now]][1]==Now;
}
void rotate(int x)
{
    int fa1=fa[x];
    int fa2=fa[fa1];
    PushDown(x);
    PushDown(fa1);
    int K=Get(x);
    int K1=Get(fa1);
    Son[fa1][K]=Son[x][K^1];
    fa[Son[fa1][K]]=fa1;
    Son[x][K^1]=fa1;
    fa[fa1]=x;
    fa[x]=fa2;
    if (fa2) Son[fa2][Son[fa2][1]==fa1]=x;
    Pushup(fa1);
    Pushup(x);
}
void Splay(int Now,int goal)
{
    for (int Fa;(Fa=fa[Now])!=goal;rotate(Now))
    if (fa[Fa]!=goal)
        if (Get(Now)==Get(Fa))
        rotate(Fa);
        else rotate(Now);
    if (!goal) rt=Now;
}
int Kth(int Now)
{
    int now=rt;
    while (1)
    {
        PushDown(now);
        if (Now<=Size[Son[now][0]])
            now=Son[now][0];
        else
        if (Now>Size[Son[now][0]])
        {        
            Now-=Size[Son[now][0]]+1;
            if (!Now)
                return now;
            now=Son[now][1];
        }
    }
}
void Reverse(int l,int r)
{
    l=Kth(l);        
    r=Kth(r+2);        
    Splay(l,0);
    Splay(r,l);    
    PushDown(rt);
    tag[Son[Son[rt][1]][0]]^=1;
}
void Write(int Now)
{
    PushDown(Now);
    if (Son[Now][0]) Write(Son[Now][0]);
    if (key[Now]!=-1e9&&key[Now]!=1e9)printf("%d ",key[Now]);
    if (Son[Now][1]) Write(Son[Now][1]);
}
int Build(int Fa,int l,int r)
{
    if (l>r) return 0;
    int mid=(l+r)>>1;
    int Now=++size;
    key[Now]=data[mid];
    fa[Now]=Fa;
    tag[Now]=0;
    Son[Now][0]=Build(Now,l,mid-1);
    Son[Now][1]=Build(Now,mid+1,r);
    Pushup(Now);
    return Now;
}
int main()
{
    int N,M;
    scanf("%d%d",&N,&M);
    data[1]=-1e9;    
    for (int i=1;i<=N;i++)
        data[i+1]=i;
    data[N+2]=1e9;
    rt=Build(0,1,N+2);
    while (M--)
    {
        int l,r;
        scanf("%d%d",&l,&r);
        Reverse(l,r);
    }
    Write(rt);
    return 0;
}
原文地址:https://www.cnblogs.com/si--nian/p/10794517.html