splay详解(二)

前言

上一节中,我们讲述了Splay的核心操作rotate与splay
本节我会教大家如何用这两个函数实现各种强大的功能
为了方便讲解,我们拿这道题做例题来慢慢分析

利用splay实现各种功能

首先,我们需要定义一些东西

各种指针

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

rotate

splay

这两个函数就不讲了,前面已经讲的挺详细了

插入

根据前面讲的,我们在插入一个数之后,需要将其旋转到根
首先
当这棵树已经没有节点的时候,我们直接新建一个节点就好

inline int newpoint(int v,int fa)//v:权值;fa:它的爸爸是谁
{
    tree[++tot].fa=fa;
    tree[tot].v=v;
    tree[tot].sum=tree[tot].rec=1;
    return tot;
}

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

void Insert(int x)
{
    int now=root;
    if(root==0) {newnode(x,0);root=tot;}//
    else
    {
        while(1)
        {
            T[now].sum++;
            if(T[now].val==x) {T[now].rec++;splay(now,root);return ;}
            int nxt=x<T[now].val?0:1;
            if(!T[now].ch[nxt])
            {
                int p=newnode(x,now);
                T[now].ch[nxt]=p;
                splay(p,root);return ;
            }
            now=T[now].ch[nxt];
        }       
    }
}

删除

删除的功能是:删除权值为v的节点
我们不难想到:我们可以先找到他的位置,再把这个节点删掉

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

这个函数可以找到权值为v的节点的位置,比较好理解,注意别忘记把找到的节点splay到根

另外我们还需要一个彻底删除的函数

update:
这个函数其实是不需要的,因为后面的节点一定会覆盖前面的节点

inline void dele(int x)
{
    tree[x].sum=tree[x].v=tree[x].rec=tree[x].fa=tree[x].ch[0]=tree[x].ch[1]=0;
    if(x==tot)  tot--;
}

接下来的任务就是怎么样才能保证删除节点后整棵树还满足二叉查找树的性质
注意:我们在查找完一个节点的时候已经将他旋转到根了,所以他左边一定都比他小,除此之外没有比他小的节点了(否则还要考虑他父亲比他小的情况)

那么此时会出现几种情况

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

最后把这个节点删掉就好

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

查询x数的排名

update in 2018.4.10
以前的自己too naive
这个函数这么写就好

int rak(int val)
{
    int pos=find(val);
    return T[ls(pos)].siz+1;
}

这个简单,如果我们找到了权值为x的节点,那么答案就是他的左子树的大小+1
否则的话根据二叉查找树的性质不断的向下走就可以,注意如果这次是向右走的话答案需要加上它左子树的大小和这个节点的rec值

int rank(int v)// 查询值为v的数的排名 
{
    int ans=0,now=root;
    while(1)
    {
        if(tree[now].v==v)    return ans+tree[tree[now].ch[0]].sum+1;
        if(now==0)  return 0;
        if(v<tree[now].v)    now=tree[now].ch[0];
        else                 ans+=tree[tree[now].ch[0]].sum+tree[now].rec,now=tree[now].ch[1];
    }
    if(now)    splay(now,root);
    return 0;
}

查询排名为x的数

这个操作就是上面那个操作的逆向操作
用used变量记录该节点以及它的左子树有多少节点
如果x>左子树的数量且< used,那么当前节点的权值就是答案
否则根据二叉查找树的性质继续向下走
同样注意在向右走的时候要更新x

int arank(int x)//查询排名为x的数是什么 
{
    int now=root;
    while(1)
    {
        int used=tree[now].sum-tree[tree[now].ch[1]].sum;
        if(x>tree[tree[now].ch[0]].sum&&x<=used)    break;
        if(x<used)    now=tree[now].ch[0];
        else    x=x-used,now=tree[now].ch[1];
    }
    splay(now,root);
    return tree[now].v;
}

求x的前驱

这个更容易,我们可以维护一个ans变量,然后对整棵树进行遍历,同时更新ans

int lower(int v)// 小于v的最大值 
{
    int now=root;
    int ans=-maxn;
    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;
}

求x的后继

这个和上一个一样,就不细讲了

int upper(int v)
{
    int now=root;
    int ans=maxn;
    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;
}

完整代码:

// luogu-judger-enable-o2
// luogu-judger-enable-o2
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#define ls(x) T[x].ch[0]
#define rs(x) T[x].ch[1]
#define fa(x) T[x].fa
#define root T[0].ch[1]
using namespace std;
const int MAXN=1e5+10,mod=10007,INF=1e9+10;
inline char nc()
{
    static char buf[MAXN],*p1=buf,*p2=buf;
    return p1==p2&&(p2=(p1=buf)+fread(buf,1,MAXN,stdin)),p1==p2?EOF:*p1++;
}
inline int read()
{
    char c=nc();int x=0,f=1;
    while(c<'0'||c>'9'){if(c=='-')f=-1;c=nc();}
    while(c>='0'&&c<='9'){x=x*10+c-'0';c=nc();}
    return x*f;
}
struct node
{
    int fa,ch[2],val,rec,sum;
}T[MAXN];
int tot=0,pointnum=0;
void update(int x){T[x].sum=T[ls(x)].sum+T[rs(x)].sum+T[x].rec;}
int ident(int x){return T[fa(x)].ch[0]==x?0:1;}
void connect(int x,int fa,int how){T[fa].ch[how]=x;T[x].fa=fa;}
void rotate(int x)
{
    int Y=fa(x),R=fa(Y);
    int Yson=ident(x),Rson=ident(Y);
    connect(T[x].ch[Yson^1],Y,Yson);
    connect(Y,x,Yson^1);
    connect(x,R,Rson);
    update(Y);update(x);
}
void splay(int x,int to)
{
    to=fa(to);
    while(fa(x)!=to)
    {
        int y=fa(x);
        if(T[y].fa==to) rotate(x);
        else if(ident(x)==ident(y)) rotate(y),rotate(x);
        else rotate(x),rotate(x);
    }
}
int newnode(int v,int f)
{
    T[++tot].fa=f;
    T[tot].rec=T[tot].sum=1;
    T[tot].val=v;
    return tot;
}
void Insert(int x)
{
    int now=root;
    if(root==0) {newnode(x,0);root=tot;}//
    else
    {
        while(1)
        {
            T[now].sum++;
            if(T[now].val==x) {T[now].rec++;splay(now,root);return ;}
            int nxt=x<T[now].val?0:1;
            if(!T[now].ch[nxt])
            {
                int p=newnode(x,now);
                T[now].ch[nxt]=p;
                splay(p,root);return ;
            }
            now=T[now].ch[nxt];
        }       
    }
}
int find(int x)
{
    int now=root;
    while(1)
    {
        if(!now) return 0;
        if(T[now].val==x) {splay(now,root);return now;}
        int nxt=x<T[now].val?0:1;
        now=T[now].ch[nxt];
    }
}
void delet(int x)
{
    int pos=find(x);
    if(!pos) return ;
    if(T[pos].rec>1) {T[pos].rec--,T[pos].sum--;return ;} 
    else
    {
        if(!T[pos].ch[0]&&!T[pos].ch[1]) {root=0;return ;}
        else if(!T[pos].ch[0]) {root=T[pos].ch[1];T[root].fa=0;return ;}
        else
        {
            int left=T[pos].ch[0];
            while(T[left].ch[1]) left=T[left].ch[1];
            splay(left,T[pos].ch[0]);
            connect(T[pos].ch[1],left,1); 
            connect(left,0,1);//
            update(left);
        }
    }
}
int rak(int x)
{
    int now=root,ans=0;
    while(1)
    {
        if(T[now].val==x) return ans+T[T[now].ch[0]].sum+1;
        int nxt=x<T[now].val?0:1;
        if(nxt==1) ans=ans+T[T[now].ch[0]].sum+T[now].rec;
        now=T[now].ch[nxt];
    }
}
int kth(int x)//排名为x的数 
{
    int now=root;
    while(1)
    {
        int used=T[now].sum-T[T[now].ch[1]].sum;
        if(T[T[now].ch[0]].sum<x&&x<=used) {splay(now,root);return T[now].val;}
        if(x<used) now=T[now].ch[0];
        else now=T[now].ch[1],x-=used;
    }
}
int lower(int x)
{
    int now=root,ans=-INF;
    while(now)
    {
        if(T[now].val<x) ans=max(ans,T[now].val);
        int nxt=x<=T[now].val?0:1;//这里需要特别注意 
        now=T[now].ch[nxt];
    }
    return ans;
}
int upper(int x)
{
    int now=root,ans=INF;
    while(now)
    {
        if(T[now].val>x) ans=min(ans,T[now].val);
        int nxt=x<T[now].val?0:1;
        now=T[now].ch[nxt];
    }
    return ans;
}
int main()
{
    #ifdef WIN32
    freopen("a.in","r",stdin);
    #else
    #endif
    int N=read();
    while(N--)
    {
        int opt=read(),x=read();
        if(opt==1) Insert(x);
        else if(opt==2) delet(x);
        else if(opt==3) printf("%d\n",rak(x));
        else if(opt==4) printf("%d\n",kth(x));
        else if(opt==5) printf("%d\n",lower(x));
        else if(opt==6) printf("%d\n",upper(x));
    } 
    return 0;
}

至此,splay最常用的几种函数就解决了,
下面来看几道裸题

例题

不知道为什么,我的splay跑的特别快,可能是脸太好了吧😂

洛谷P2234 [HNOI2002]营业额统计

http://www.cnblogs.com/zwfymqz/p/7896128.html

洛谷P2286 [HNOI2004]宠物收养场

http://www.cnblogs.com/zwfymqz/p/7895794.html

posted @ 2017-11-26 08:47 自为风月马前卒 阅读(...) 评论(...) 编辑 收藏

Contact with me

……