Splay详解

平衡树实际很简单的

如下讲解都以Luogu P3369 【模板】普通平衡树为例

我不会带指针的Splay,因此我就写非指针型的Splay

Splay是基于二叉查找树(bst)实现的

什么是二叉查找树呢?就是一棵树呗,可是这棵树知足性质:一个节点的左孩子必定比它小,右孩子必定比它大

好比:

1101696-20171125191307484-1881794250.png

这就是一棵最基本二叉查找树

对于每次插入,它的指望复杂度大约是log^2 n级别的,可是存在极端状况,好比9999999 9999998 9999997.....1这种数据,会直接被卡成n^2级别

在这种状况下,平衡树出现了!

1.定义Splay

struct node
{
    int v;//权值
    int fa;//父亲节点
    int ch[2];//0表明左儿子,1表明右儿子
    int rec;//这个权值的节点出现的次数
    int sum;//子节点的数量
}tree[N];//N为节点最多有多少
int tot;//tot表示不算重复的有多少节点

2.Splay的核心

Rotate

首先考虑一下,咱们要把一个点挪到根,那咱们首先要知道怎么让一个点挪到它的父节点

状况1:

当X是Y的左孩子时

1101696-20171125194733656-1618296206.png

ABC实际能够是子树,但这里假设ABC都是点

这时候若是咱们让X成为Y的父亲,只会影响到3组点的关系

B与X,X与Y,X与R

根据二叉排序树的性质

B会成为Y的左儿子

Y会成为X的右儿子

X会成为R的儿子,具体是什么儿子,这个要看Y是R的啥儿子

1101696-20171125200053250-822796586.png

状况2:

当X是Y的右孩子

本质上是和状况1同样的qaq

1101696-20171125200211625-2022743205.png

旋转后变成

1101696-20171125200354937-579910866.png

能不能把这两种状况合并呢qaq?结果是确定的

咱们须要一个函数来肯定这个节点是他父节点的左孩子仍是右孩子

inline bool findd(register int x)
{
    return tree[tree[x].fa].ch[0]==x?0:1;
}

若是是左孩子的话会返回0,右孩子会返回1

那么咱们不可贵到R,Y,X这三个节点的信息

int Y=tree[x].fa;
int R=tree[Y].fa;
int Yson=findd(x);
int Rson=findd(Y);

B的状况咱们能够根据X的状况推算出来,根据^运算的性质,0^1=1,1^1=0,2^1=3,3^1=2,并且B相对于X的位置必定是与X相对于Y的位置是相反的

(不然在旋转的过程当中不会对B产生影响)

int B=tree[x].ch[Yson^1];

而后咱们考虑链接的过程

根据上面的图,不可贵到(本身向上翻qaq)

1.B成为Y的哪一个儿子与X是Y的哪一个儿子是同样的

2.Y成为X的哪一个儿子与X是Y的哪一个儿子相反

3.X成为R的哪一个儿子与Y是R的哪一个儿子相同

connect(B,Y,Yson);
connect(Y,x,Yson^1);
connect(x,R,Rson);

connect函数也很好写

inline void connect(register int x,register int fa,register int son) //把x转为fa的son(son是0/1,表示左孩子或右孩子)
{
    tree[x].fa=fa;
    tree[fa].ch[son]=x;
}

Rotate代码总览(旋转完不要忘了update):

inline void update(register int x)
{
    tree[x].sum=tree[tree[x].ch[0]].sum+tree[tree[x].ch[1]].sum+tree[x].rec;
}
inline bool findd(register int x)
{
    return tree[tree[x].fa].ch[0]==x?0:1;
}
inline void connect(register int x,register int fa,register int son) //把x转为fa的son(son是0/1,表示左孩子或右孩子)
{
    tree[x].fa=fa;
    tree[fa].ch[son]=x;
} 
inline void rotate(register int x)
{
    int Y=tree[x].fa;
    int R=tree[Y].fa;
    int Yson=findd(x);
    int Rson=findd(Y);
    int B=tree[x].ch[Yson^1];
    connect(B,Y,Yson);
    connect(Y,x,Yson^1);
    connect(x,R,Rson);
    update(Y),update(x);
}

Splay

Splay(x,to)是实现把x节点搬到to节点

最简单的办法,对于x这个节点,每次上旋直到to

可是!

毒瘤的出题人能够构造数据把上面的这种方法卡到n^2 qaq*

下面咱们介绍一下双旋的Splay

这里的状况有不少,可是总的来讲就三种状况

1.to是x的爸爸,

这样的话吧x旋转上去就好

if(tree[tree[x].fa].fa==to)
    rotate(x);

2.x和他爸爸和他爸爸的爸爸在一条线上(文字游戏)

其实就是findd(x)=findd(tree[x].fa)

这时候先把Y旋转上去,再把X旋转上去就好

if(findd(x)==find(tree[x].fa))
    rotate(tree[x].fa),rotate(x);

3.x和他爸爸和他爸爸的爸爸不在一条线上(和2相反)

这时候把X旋转两次就好

spaly函数的代码:

inline void splay(register int x,register int to)
{
    to=tree[to].fa;
    while(tree[x].fa!=to)
    {
        int y=tree[x].fa;
        if(tree[y].fa==to)
            rotate(x);
        else if(findd(x)==findd(y))
            rotate(y),rotate(x);
        else
            rotate(x),rotate(x);
    }   
}

Splay的核心代码到此结束

剩下的就是一些其余的东西(虽然说有的也挺重要qaq)

3.其余的一些函数

insert

根据前面讲的,咱们在插入一个数以后,须要将其旋转到根

首先,当这棵树已经没有节点的时候,咱们直接新建一个节点就好

inline int newpoint(register int v,register int fa)
{
    tree[++tot].fa=fa;
    tree[tot].v=v;
    tree[tot].sum=tree[tot].rec=1;
    return tot; 
}

而后,当这可树有节点的时候,咱们根据二叉查找树的性质,不断向下走,直到找到一个能够插入的点,注意在走的时候须要更新一个每一个节点的sum值

inline void Insert(register int x)
{
    int now=tree[0].ch[1];
    if(tree[0].ch[1]==0)
    {
        newpoint(x,0);
        tree[0].ch[1]=tot;
    }
    else
    {
        while(19260817)
        {
            ++tree[now].sum;
            if(tree[now].v==x)
            {
                ++tree[now].rec;
                splay(now,tree[0].ch[1]);
                return;
            }
            int nxt=x<tree[now].v?0:1;
            if(!tree[now].ch[nxt])
            {
                int p=newpoint(x,now);
                tree[now].ch[nxt]=p;
                splay(p,tree[0].ch[1]);
                return;
            }
            now=tree[now].ch[nxt];
        }
    }
}

delete

删除的功能是:删除权值为v的节点

咱们不难想到:咱们能够先找到他的位置,再把这个节点删掉

找位置用find函数,不要和rotate的findd搞混

inline int find(register int v)
{
    int now=tree[0].ch[1];
    while(19260817)
    {
        if(tree[now].v==v)
        {
            splay(now,tree[0].ch[1]);
            return now;
        }
        int nxt=v<tree[now].v?0:1;
        if(!tree[now].ch[nxt])
            return 0;
        now=tree[now].ch[nxt];
    }
}

下面咱们须要删除函数

怎么样才能保证删除节点后整棵树还知足二叉查找树的性质

此时会出现几种状况

1.权值为v的节点已经出现过
这时候直接把他的rec和sum减去1就好
2.本节点没有左右儿子
这样的话就成了一棵空树
3.本节点没有左儿子
直接把他的右儿子设置成根
4.既有左儿子,又有右儿子
在它的左儿子中找到最大的,旋转到根,把它的右儿子当作根(也就是它最大的左儿子)的右儿子

最后把这个节点删掉就好

delete的代码

inline void delet(register int x)
{
    int pos=find(x);
    if(!pos)
        return;
    if(tree[pos].rec>1)
    {
        --tree[pos].rec;
        --tree[pos].sum;
    }
    else
    {
        if(!tree[pos].ch[0]&&!tree[pos].ch[1])
            tree[0].ch[1]=0;
        else if(!tree[pos].ch[0])
        {
            tree[0].ch[1]=tree[pos].ch[1];
            tree[tree[0].ch[1]].fa=0;
        }
        else
        {
            int left=tree[pos].ch[0];
            while(tree[left].ch[1])
                left=tree[left].ch[1];
            splay(left,tree[pos].ch[0]);
            connect(tree[pos].ch[1],left,1);
            connect(left,0,1);
            update(left);
        }
    }
}

rank

1.查询x数的排名

十分简短

inline int rank(register int v)
{
    int pos=find(v);
    return tree[tree[pos].ch[0]].sum+1;
}

2.查询排名为x的数

这个操做就是上面那个操做的逆向操做

inline int arank(register int x)
{
    int now=tree[0].ch[1];
    while(19260817)
    {
        int used=tree[now].sum-tree[tree[now].ch[1]].sum;
        if(x>tree[tree[now].ch[0]].sum&&x<=used)
        {
            splay(now,tree[0].ch[1]);
            return tree[now].v;
        }
        if(x<used)
            now=tree[now].ch[0];
        else
            x-=used,now=tree[now].ch[1];
    }
}

求前驱和后继

前驱

这个更容易,咱们能够维护一个ans变量,而后对整棵树进行遍历,同时更新ans

inline int lower(register int v)
{
    int now=tree[0].ch[1];
    int ans=-inf;
    while(now)
    {
        if(tree[now].v<v&&tree[now].v>ans)
            ans=tree[now].v;
        if(v>tree[now].v)
            now=tree[now].ch[1];
        else
            now=tree[now].ch[0];
    }
    return ans;
}

后继

和前驱差很少

inline int upper(register int v)
{
    int now=tree[0].ch[1];
    int ans=inf;
    while(now)
    {
        if(tree[now].v>v&&tree[now].v<ans)
            ans=tree[now].v;
        if(v<tree[now].v)
            now=tree[now].ch[0];
        else
            now=tree[now].ch[1];
    }
    return ans;
}

4.Spaly总体代码

#pragma GCC optimize("O3")
#include <bits/stdc++.h>
#define N 100005
#define inf 1000000005
using namespace std;
inline int read()
{
    register int x=0,f=1;register char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9')x=(x<<3)+(x<<1)+ch-'0',ch=getchar();
    return x*f;
}
inline void write(register int x)
{
    if(!x)putchar('0');if(x<0)x=-x,putchar('-');
    static int sta[36];int tot=0;
    while(x)sta[tot++]=x%10,x/=10;
    while(tot)putchar(sta[--tot]+48);
}
struct node
{
    int v;
    int fa;
    int ch[2];
    int rec;
    int sum;
}tree[N];
int tot;
inline void update(register int x)
{
    tree[x].sum=tree[tree[x].ch[0]].sum+tree[tree[x].ch[1]].sum+tree[x].rec;
}
inline bool findd(register int x)
{
    return tree[tree[x].fa].ch[0]==x?0:1;
}
inline void connect(register int x,register int fa,register int son) //把x转为fa的son(son是0/1,表示左孩子或右孩子)
{
    tree[x].fa=fa;
    tree[fa].ch[son]=x;
} 
inline void rotate(register int x)
{
    int Y=tree[x].fa;
    int R=tree[Y].fa;
    int Yson=findd(x);
    int Rson=findd(Y);
    int B=tree[x].ch[Yson^1];
    connect(B,Y,Yson);
    connect(Y,x,Yson^1);
    connect(x,R,Rson);
    update(Y),update(x);
}
inline void splay(register int x,register int to)
{
    to=tree[to].fa;
    while(tree[x].fa!=to)
    {
        int y=tree[x].fa;
        if(tree[y].fa==to)
            rotate(x);
        else if(findd(x)==findd(y))
            rotate(y),rotate(x);
        else
            rotate(x),rotate(x);
    }   
}
inline int newpoint(register int v,register int fa)
{
    tree[++tot].fa=fa;
    tree[tot].v=v;
    tree[tot].sum=tree[tot].rec=1;
    return tot; 
}
inline void Insert(register int x)
{
    int now=tree[0].ch[1];
    if(tree[0].ch[1]==0)
    {
        newpoint(x,0);
        tree[0].ch[1]=tot;
    }
    else
    {
        while(19260817)
        {
            ++tree[now].sum;
            if(tree[now].v==x)
            {
                ++tree[now].rec;
                splay(now,tree[0].ch[1]);
                return;
            }
            int nxt=x<tree[now].v?0:1;
            if(!tree[now].ch[nxt])
            {
                int p=newpoint(x,now);
                tree[now].ch[nxt]=p;
                splay(p,tree[0].ch[1]);
                return;
            }
            now=tree[now].ch[nxt];
        }
    }
}
inline int find(register int v)
{
    int now=tree[0].ch[1];
    while(19260817)
    {
        if(tree[now].v==v)
        {
            splay(now,tree[0].ch[1]);
            return now;
        }
        int nxt=v<tree[now].v?0:1;
        if(!tree[now].ch[nxt])
            return 0;
        now=tree[now].ch[nxt];
    }
}
inline void delet(register int x)
{
    int pos=find(x);
    if(!pos)
        return;
    if(tree[pos].rec>1)
    {
        --tree[pos].rec;
        --tree[pos].sum;
    }
    else
    {
        if(!tree[pos].ch[0]&&!tree[pos].ch[1])
            tree[0].ch[1]=0;
        else if(!tree[pos].ch[0])
        {
            tree[0].ch[1]=tree[pos].ch[1];
            tree[tree[0].ch[1]].fa=0;
        }
        else
        {
            int left=tree[pos].ch[0];
            while(tree[left].ch[1])
                left=tree[left].ch[1];
            splay(left,tree[pos].ch[0]);
            connect(tree[pos].ch[1],left,1);
            connect(left,0,1);
            update(left);
        }
    }
}
inline int rank(register int v)
{
    int pos=find(v);
    return tree[tree[pos].ch[0]].sum+1;
}
inline int arank(register int x)
{
    int now=tree[0].ch[1];
    while(19260817)
    {
        int used=tree[now].sum-tree[tree[now].ch[1]].sum;
        if(x>tree[tree[now].ch[0]].sum&&x<=used)
        {
            splay(now,tree[0].ch[1]);
            return tree[now].v;
        }
        if(x<used)
            now=tree[now].ch[0];
        else
            x-=used,now=tree[now].ch[1];
    }
}
inline int lower(register int v)
{
    int now=tree[0].ch[1];
    int ans=-inf;
    while(now)
    {
        if(tree[now].v<v&&tree[now].v>ans)
            ans=tree[now].v;
        if(v>tree[now].v)
            now=tree[now].ch[1];
        else
            now=tree[now].ch[0];
    }
    return ans;
}
inline int upper(register int v)
{
    int now=tree[0].ch[1];
    int ans=inf;
    while(now)
    {
        if(tree[now].v>v&&tree[now].v<ans)
            ans=tree[now].v;
        if(v<tree[now].v)
            now=tree[now].ch[0];
        else
            now=tree[now].ch[1];
    }
    return ans;
}
int main()
{
    int m=read();
    while(m--)
    {
        int opt=read(),x=read();
        if(opt==1)
            Insert(x);
        else if(opt==2)
            delet(x);
        else if(opt==3)
        {
            write(rank(x));
            printf("\n");
        }
        else if(opt==4)
        {
            write(arank(x));
            printf("\n");
        }
        else if(opt==5)
        {
            write(lower(x));
            printf("\n");
        }
        else
        {
            write(upper(x));
            printf("\n");
        }
    }
    return 0;
}

5.相关题目

1.Luogu P2234 [HNOI2002]营业额统计

平衡树板题

2.Luogu P1503 鬼子进村

平衡树板题

3.Luogu P3871 [TJOI2010]中位数

平衡树板题

4.Luogu P1533 可怜的狗狗

莫队+平衡树苟过

5.Luogu P2073 送花

平衡树板题

以上是splay的基本应用qaq

还有一种操做没讲,就是如何进行区间操做

实现起来很简单

假设咱们要在[l,r]之间上搞事情,咱们首先把l的前驱旋转到根节点,再把r的后继转到根节点的右儿子

那么此时根节点右儿子的左儿子表明的就是区间[l,r]

这应该很好理解qaq

而后就能够像线段树的lazy标记同样,给区间l,rl,r打上标记,延迟更新,好比区间反转的时候更新的时候直接交换左右儿子

咱们下面以P3391 【模板】文艺平衡树(Splay)为例

这里有一个巧:若是一个区间被打了两次,那么就至关于不打

因此咱们用一个bool变量来储存该节点是否须要被旋转

pushdown函数能够这么写(rev就是翻转标记)

inline void pushdown(register int x)
{
    if(tree[x].rev)
    {
        swap(tree[x].ch[0],tree[x].ch[1]);
        tree[tree[x].ch[0]].rev^=1;
        tree[tree[x].ch[1]].rev^=1;    
        tree[x].rev=0;
    }
}

这道题完整代码

#include <bits/stdc++.h>
#define N 100005
#define inf 0x7fffff
using namespace std;
inline int read()
{
    register int x=0,f=1;register char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9')x=(x<<3)+(x<<1)+ch-'0',ch=getchar();
    return x*f;
}
inline void write(register int x)
{
    if(!x)putchar('0');if(x<0)x=-x,putchar('-');
    static int sta[36];int cnt=0;
    while(x)sta[cnt++]=x%10,x/=10;
    while(cnt)putchar(sta[--cnt]+48);
}
int n,m;
struct node{
    int fa,ch[2],tot;
    bool rev;
}tree[N];
int root,PosL,PosR;
inline bool findd(register int x)
{
    return x==tree[tree[x].fa].ch[1];
}
inline void connect(register int x,register int fa,register int son)
{
    tree[x].fa=fa;
    tree[fa].ch[son]=x;
}
inline void update(register int x)
{
    tree[x].tot=tree[tree[x].ch[0]].tot+tree[tree[x].ch[1]].tot+1;
}
inline void rotate(register int x)
{
    int Y=tree[x].fa;
    if(Y==root)
        root=x;
    int R=tree[Y].fa;
    int Yson=findd(x);
    int Rson=findd(Y);
    int B=tree[x].ch[Yson^1];
    connect(B,Y,Yson);
    connect(Y,x,Yson^1);
    connect(x,R,Rson);
    update(Y);
    update(x);
}
inline void splay(register int x,register int to)
{
    while(tree[x].fa!=to)
    {
        int y=tree[x].fa;
        if(tree[y].fa==to)
            rotate(x);
        else if(findd(x)==findd(y))
            rotate(y),rotate(x);
        else
            rotate(x),rotate(x);
    }
    update(x);
}
inline int buildsplay(register int l,register int r)
{
    if(l>r)
        return 0;
    int mid=l+r>>1;
    connect(buildsplay(l,mid-1),mid,0);
    connect(buildsplay(mid+1,r),mid,1);
    tree[mid].rev=0;
    update(mid);
    return mid;
}
inline void pushdown(register int x)
{
    if(tree[x].rev)
    {
        swap(tree[x].ch[0],tree[x].ch[1]);
        tree[tree[x].ch[0]].rev^=1;
        tree[tree[x].ch[1]].rev^=1;
        tree[x].rev=0;
    }
}
inline int find(register int x)
{
    int now=root;
    --x;
    pushdown(now);
    while(x!=tree[tree[now].ch[0]].tot)
    {
        if(tree[tree[now].ch[0]].tot<x)
            x-=tree[tree[now].ch[0]].tot+1,now=tree[now].ch[1];
        else
            now=tree[now].ch[0];
        pushdown(now);
    }
    return now;
}
inline void print(register int now)
{
    if(!now)
        return;
    pushdown(now);
    print(tree[now].ch[0]);
    if(now!=1&&now!=n+2)
        write(now-1),putchar(' ');
    print(tree[now].ch[1]);
}
int main()
{
    n=read(),m=read();
    root=buildsplay(1,n+2);
    while(m--)
    {
        int l=read(),r=read();
        PosL=find(l);
        splay(PosL,0);
        PosR=find(r+2);
        splay(PosR,root);
        tree[tree[PosR].ch[0]].rev^=1;
    }
    print(root);
    return 0;
 }
相关文章
相关标签/搜索