• BZOJ 3224: Tyvj 1728 普通平衡树 or 洛谷 P3369 【模板】普通平衡树-Splay树模板题


    3224: Tyvj 1728 普通平衡树

    Time Limit: 10 Sec  Memory Limit: 128 MB
    Submit: 22483  Solved: 10130
    [Submit][Status][Discuss]

    Description

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

    Input

    第一行为n,表示操作的个数,下面n行每行有两个数opt和x,opt表示操作的序号(1<=opt<=6)

    Output

    对于操作3,4,5,6每行输出一个数,表示对应答案

    Sample Input

    10
    1 106465
    4 1
    1 317721
    1 460929
    1 644985
    1 84185
    1 89851
    6 81968
    1 492737
    5 493598

    Sample Output

    106465
    84185
    492737

    HINT

    1.n的数据范围:n<=100000

    2.每个数的数据范围:[-2e9,2e9]
     
     
     
    题目就是一个splay的模板,直接贴代码,代码里写了注释。
     
    代码:
      1 #include<iostream>
      2 #include<cstdio>
      3 #include<cstring>
      4 #include<algorithm>
      5 #include<bitset>
      6 #include<cassert>
      7 #include<cctype>
      8 #include<cmath>
      9 #include<cstdlib>
     10 #include<ctime>
     11 #include<deque>
     12 #include<iomanip>
     13 #include<list>
     14 #include<map>
     15 #include<queue>
     16 #include<set>
     17 #include<stack>
     18 #include<vector>
     19 using namespace std;
     20 typedef long long ll;
     21 
     22 const double PI=acos(-1.0);
     23 const double eps=1e-6;
     24 const ll mod=1e9+7;
     25 const int inf=0x3f3f3f3f;
     26 const int maxn=1e6+10;
     27 const int maxm=100+10;
     28 #define ios ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
     29 
     30 /*
     31 平衡树的本质其实是二叉搜索树,所以很多操作是基于二叉搜索树的操作。
     32 
     33 splay的本质是rotate,旋转其实只是为了保证二叉搜索树的平衡性。
     34 
     35 所有的操作一定都满足二叉搜索树的性质,所有改变父子关系的操作一定要update
     36 */
     37 
     38 int ch[maxn][2],f[maxn],size[maxn],cnt[maxn],key[maxn];
     39 /*
     40 f[i]表示i的父节点,ch[i][0]表示i的左儿子,ch[i][1]表示i的右儿子,key[i]表示i的关键字(即节点i代表的那个数)
     41 cnt[i]表示i的关键字出现的次数(相当于权值),size[i]表示包括i在内的子树的大小
     42 */
     43 int sz,root;//sz为整棵树的大小,root为整棵树的根
     44 
     45 inline void clear(int x)//将当前点的各项清零(用于删除之后)
     46 {
     47     ch[x][0]=ch[x][1]=f[x]=size[x]=cnt[x]=key[x]=0;
     48 }
     49 
     50 inline bool get(int x)//判断当前点是它父节点的左儿子还是右儿子
     51 {
     52     return ch[f[x]][1]==x;
     53 }
     54 
     55 inline void update(int x)//更新当前点的size值(用于发生修改之后)
     56 {
     57     if(x){
     58         size[x]=cnt[x];//x的个数
     59         if(ch[x][0]) size[x]+=size[ch[x][0]];//加上左右儿子的size
     60         if(ch[x][1]) size[x]+=size[ch[x][1]];
     61     }
     62 }
     63 
     64 inline void rotate(int x)//伸展操作,旋转
     65 {
     66     int old=f[x],oldf=f[old],whichx=get(x);//找爸爸,爷爷,儿子  下面的以向右转为例子,反过来也一样的
     67     ch[old][whichx]=ch[x][whichx^1];f[ch[old][whichx]]=old;//x的右儿子变为x的爸爸的左儿子,x的右儿子的爸爸变为x的爸爸
     68     ch[x][whichx^1]=old;f[old]=x;//x的右儿子变为x以前的爸爸,x以前的爸爸的爸爸变为x
     69     f[x]=oldf;//x的爸爸变为x以前的爷爷
     70     if(oldf)
     71         ch[oldf][ch[oldf][1]==old]=x;//以前x的爷爷的儿子是x以前的爸爸,现在更新为x
     72     update(old);update(x);//更新
     73 }
     74 
     75 /*
     76 splay的过程中需要分类讨论,如果是三点一线的情况(x,x的父亲,x的祖父) 需要先rotate x的父亲,否则需要先rotate本身,(否则会形成单旋使平衡树失衡)
     77 */
     78 
     79 inline void splay(int x)//splay是rotate的发展,一直rotate到目标状态,如果有一个确定的目标状态,也可以传两个参数,一般是直接旋转到根
     80 {
     81     for(int fa;fa=f[x];rotate(x))
     82         if(f[fa])
     83             rotate(get(x)==get(fa)?fa:x);
     84         root=x;
     85 }
     86 
     87 inline void insert(int x)//插入操作
     88 {
     89     if(root==0){
     90         sz++;ch[sz][0]=ch[sz][1]=f[sz]=0;
     91         root=sz;size[sz]=cnt[sz]=1;key[sz]=x;return ;
     92     }
     93     int now=root,fa=0;
     94     while(1){
     95         if(x==key[now]){//如果这个数在树中已经出现了
     96             cnt[now]++;update(now);update(fa);splay(now);break;
     97         }
     98         fa=now;now=ch[now][key[now]<x];
     99         if(now==0){//如果到了最底下,还是没找到,说明要插入新的值,整棵树的大小+1,新节点的左儿子右儿子(虽然为空)父节点等各个值都一一对应,然后做一下他父亲的update(做自己的没必要),做一下splay
    100             sz++;ch[sz][0]=ch[sz][1]=0;
    101             f[sz]=fa;size[sz]=cnt[sz]=1;
    102             ch[fa][key[fa]<x]=sz;key[sz]=x;
    103             update(fa);splay(sz);break;
    104         }
    105     }
    106 }
    107 
    108 inline int find(int x)//查找x的排名
    109 {
    110     int now=root,ans=0;
    111     while(1){
    112         if(x<key[now]) now=ch[now][0];//x比当前的小,继续找左子树
    113         else{
    114             ans+=(ch[now][0]?size[ch[now][0]]:0);
    115             if(x==key[now]){
    116                 splay(now);return ans+1;
    117             }
    118             ans+=cnt[now];
    119             now=ch[now][1];
    120         }
    121     }
    122 }
    123 
    124 inline int findx(int x)//找到排名为x的点
    125 {
    126     int now=root;
    127     while(1){
    128         if(ch[now][0]&&x<=size[ch[now][0]])
    129             now=ch[now][0];
    130         else{
    131             int temp=(ch[now][0]?size[ch[now][0]]:0)+cnt[now];
    132             if(x<=temp) return key[now];
    133             x-=temp;now=ch[now][1];
    134         }
    135     }
    136 }
    137 
    138 /*
    139 因为在做insert操作之后做了一遍splay,这就意味着我们把x已经splay到根了,所以pre/next操作就很好实现了
    140 */
    141 
    142 inline int pre()//求x的前驱(其实就是求x的左子树的最右边的一个节点)
    143 {
    144     int now=ch[root][0];
    145     while(ch[now][1]) now=ch[now][1];
    146     return now;
    147 }
    148 
    149 inline int next()//求x的后继(其实就是求x的右子树的左边的一个节点)
    150 {
    151     int now=ch[root][1];
    152     while(ch[now][0]) now=ch[now][0];
    153     return now;
    154 }
    155 
    156 inline void del(int x)//删除操作
    157 {
    158     int whatever=find(x);//find一下,目的是将x旋转到根
    159     if(cnt[root]>1){cnt[root]--;update(root);return ;}//现在x为根,如果满足cnt[root]>1,即不只有一个x的话,直接-1
    160     if(!ch[root][0]&&!ch[root][1]) {clear(root);root=0;return ;}//如果root没有孩子,说明树上只有一个x,直接clear
    161     if(!ch[root][0]){//如果root只有左儿子或者只有右儿子,直接clear root,然后把唯一的儿子当成根,(f变为0,root变为唯一的儿子)
    162         int oldroot=root;root=ch[root][1];;f[root]=0;clear(oldroot);return ;
    163     }
    164     else if(!ch[root][1]){
    165         int oldroot=root;root=ch[root][0];f[root]=0;clear(oldroot);return ;
    166     }
    167     //其他的就是有两个儿子的情况 如果有两个儿子的话,首先我们要先选一个根,自然是x的前驱或后继,这里我们选择前驱,然后把前驱旋转到根节点,然后再把x原来的右子树当做它的右子树,update维护一下就行。
    168     int leftbig=pre(),oldroot=root;//找到新根,也就是x的前驱,将其旋转到根,然后将原来的x的右儿子接到新根的右子树上(此操作需要改变父子关系)实际上就是把x删除,不要忘记update新根
    169     splay(leftbig);
    170     ch[root][1]=ch[oldroot][1];
    171     f[ch[oldroot][1]]=root;
    172     clear(oldroot);
    173     update(root);
    174 }
    175 
    176 int main()
    177 {
    178     int n,opt,x;
    179     scanf("%d",&n);
    180     for(int i=1;i<=n;i++){
    181         scanf("%d%d",&opt,&x);
    182         switch(opt){
    183             case 1: insert(x); break;//插入x数
    184             case 2: del(x); break;//删除x数(若有多个相同的数,因只删除一个)
    185             case 3: printf("%d
    ",find(x)); break;//查询x数的排名(若有多个相同的数,因输出最小的排名)
    186             case 4: printf("%d
    ",findx(x)); break;//查询排名为x的数
    187             case 5: insert(x); printf("%d
    ",key[pre()]); del(x); break;// 求x的前驱(前驱定义为小于x,且最大的数)
    188             case 6: insert(x);printf("%d
    ",key[next()]); del(x); break;//求x的后继(后继定义为大于x,且最小的数)
    189         }
    190     }
    191 }

    先这样。。。

     
  • 相关阅读:
    Linux的常用用法
    docker入门实践01
    airflow安装rest api插件发现airflow webserver服务不能启动的解决办法
    27.Spark中transformation的介绍
    1.Cloudera Manager安装
    win10系统不能ping通vmware虚假机解决办法
    在airflow的BashOperator中执行docker容器中的脚本容易忽略的问题
    AirFlow后台运行调度程序
    Airflow怎么删除系统自带的DAG任务
    airflow删除dag不在页面显示
  • 原文地址:https://www.cnblogs.com/ZERO-/p/9606696.html
Copyright © 2020-2023  润新知