题解:一道很模板的Splay题-洛谷P3369

老习惯,不说了
(正文中有个Splay 快速入门,博主认为写得很好,有暂时还不回Splay的萌新可以去学一下哦!)

题目描述
您需要写一种数据结构(可参考题目标题),来维护一些数,其中需要提供以下操作:
插入x数
删除x数(若有多个相同的数,因只删除一个)
查询x数的排名(排名定义为比当前数小的数的个数+1。若有多个相同的数,因输出最小的排名)
查询排名为x的数
求x的前驱(前驱定义为小于x,且最大的数)
求x的后继(后继定义为大于x,且最小的数)

输入输出格式
输入格式:
第一行为n,表示操作的个数,下面n行每行有两个数opt和x,opt表示操作的序号( 1≤opt≤6 )
输出格式:
对于操作3,4,5,6每行输出一个数,表示对应答案
输入输出样例
输入样例#1:
10
1 106465
4 1
1 317721
1 460929
1 644985
1 84185
1 89851
6 81968
1 492737
5 493598
输出样例#1:
106465
84185
492737
说明
时空限制:1000ms,128M
1.n的数据范围: n≤100000 n \leq 100000 n≤100000
2.每个数的数据范围: [−107,107][-{10}^7, {10}^7][−107,107]

控制不住自己说点心路历程,博主学了两个晚上终于把Splay给学会了,学的博主真是欲仙欲死,不过还好学会就好,其实这应该是一道平衡树的题目的,只不过被我们强行用Splay给A了,推荐大家去学一学Treap吧(看看博主的也不错哦)
在此如果大家有任何疑问的话,可以与博主讨论,在此给大家安利一个博主是从哪里学来的博客(Splay快速入门),相信大家看了之后,画一画就可以弄懂Splay的基础操作了!
好了,话不多说看代码,代码里有详细的注释,不懂一定要发讨论这不仅是你的进步,也是大家的进步!(在此博主谢谢大家了->->)

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
const int _=1e5+5;
int root,n,tot;
struct hand
{
    int kid[2];//左右儿子
    int dad;//父节点
    int cnt;//数量
    int val;//int size;//儿子数量
}tr[_<<2];
inline void js(int u)//统计儿子数
{
    tr[u].size=tr[tr[u].kid[0]].size+tr[tr[u].kid[1]].size+tr[u].cnt;
}
void rotate(int x)//旋转
{
    int y=tr[x].dad;
    int z=tr[y].dad;
    int k=tr[y].kid[1]==x;//xy的左或右儿子
    tr[z].kid[tr[z].kid[1]==y]=x;//x放在y的原位置上
    tr[x].dad=z;//换一下x的父亲
    tr[y].kid[k]=tr[x].kid[k^1];//把原本属于x的儿子且不同于x属于y的儿子方向的儿子赋在该属于x属于y的位置上
    tr[tr[x].kid[k^1]].dad=y;//换一下上一行修改的那个点的父亲
    tr[x].kid[k^1]=y;//y放在x的原位置上
    tr[y].dad=x;
    //注意原本属于x的儿子且属于x属于y方向的儿子是不需要动的!!!还有一个点也是不要动的
    js(y);js(x);//所以说顺序不能换,换了的话x的size就变了
}
//若stand是虚根,则把x转到根节点上,否则转到Stand的儿子上
void Splay(int x,int Stand)
{
    while(tr[x].dad!=Stand)
    {
        int y=tr[x].dad,z=tr[y].dad;
        if(z!=Stand)
        {
            (tr[z].kid[0]==y)^(tr[y].kid[0]==x)?rotate(x):rotate(y);
        }
/*
  6种情况下合并为三种     
        1      1            1        1             1   1
       /        \          /          \           /     \
      2          2        2            2         2       2
     /            \        \          /     y已是根节点,只能转x
    3              3        3        3
  (Num1)      (Num2)      (Num3)   (Num4)
      先转yx                先转xx
*/
        //由此可见,x必须转
        rotate(x);
    }
    if(!Stand)root=x;
}
void Insert(int x)
{
    int u=root,dad=0;
    while(u&&tr[u].val!=x)//当u存在并且没有移动到到目前的值
    {
        dad=u;
        u=tr[u].kid[x>tr[u].val];//二分查找
    }
    //把x要放的位置找到
    if(u)tr[u].cnt++;//x已经存在过了
    else
    {
        u=++tot;//新节点的位置
        if(dad)//如果父节点非根
            tr[dad].kid[x>tr[dad].val]=u;
        tr[tot].kid[0]=tr[tot].kid[1]=0;//不存在儿子
        tr[tot].dad=dad;tr[tot].val=x;
        tr[tot].cnt=1;tr[tot].size=1;
    }//OK了,要插入的点已经进去了
    Splay(u,0);//记得操作后要保证平衡哦
}
void Find(int x)//查找x的位置,并将其旋转到根节点
{
    int u=root;
    if(!u)return;//不存在节点,无法查找
    while(tr[u].kid[x>tr[u].val]&&x!=tr[u].val)//当存在x且当前位置的x值不为1
        u=tr[u].kid[x>tr[u].val];//跳到儿子去咯
    Splay(u,0);//把当前位置转移到根节点
}//SYC说Splay每次操作都要把x转到根节点上去
//所以是棵动态树,神奇!
int Next(int x,int op)//查找前驱/后继
{
    Find(x);
    int u=root;
    //op==1为后继,op==0为前驱
    if((tr[u].val>x&&op)||(tr[u].val<x&&!op))return u;
    u=tr[u].kid[op];
    while(tr[u].kid[op^1])u=tr[u].kid[op^1];
    //若要找后继,则先跳右节点,然后一直跳左跳到跳不动为止,前驱相反
    return u;
}
void Delete(int x)
{
    int last=Next(x,0);//查找前驱
    int next=Next(x,1);//查找后继
    Splay(last,0);Splay(next,last);
    //将前驱旋到根节点上,r然后将后继旋到前驱的儿子下,因此后继成了前驱的右儿子,x是后继的左儿子且儿子为叶子节点
    int del=tr[next].kid[0];
    if(tr[del].cnt>1)
    {
        tr[del].cnt--;
        Splay(del,0);//别忘记旋到根节点上去哦
    }
    else
        tr[next].kid[0]=0;//清除该节点
}
int K_th(int x)
{
    int u=root;
    if(tr[u].size<x)return 0;//都不存在这么多数
    while(1)
    {
        int y=tr[u].kid[0];
        if(x>tr[y].size+tr[u].cnt)//排名在u的后面,在右子树中找
        {
            x-=tr[y].size+tr[u].cnt;
            u=tr[u].kid[1];
        }
        else if(tr[y].size>=x)//排名在u的前面,在左子树中找
                u=y;
        else return tr[u].val;
    }
}
int main()
{
    Insert(-123456789);//处理前驱
    Insert(123456789);//处理后继
    //这样就不用处理莫名其妙的细节咯
    scanf("%d",&n);
    while(n--)
    {
        int op,x;
        scanf("%d",&op);
        scanf("%d",&x);
        if(op==1)Insert(x);
        if(op==2)Delete(x);
        if(op==3)
        {
            Find(x);
            printf("%d\n",tr[tr[root].kid[0]].size);
        }
        if(op==4)
        {
            printf("%d\n",K_th(x+1));
        }
        if(op==5)
        {
            printf("%d\n",tr[Next(x,0)].val);
        }
        if(op==6)
        {
            printf("%d\n",tr[Next(x,1)].val);
        }
    }
    return 0;
}

Thanks for your attention.

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值