[Splay伸展树]splay树入门级教程

首先声明,本教程的对象是完全没有接触过splay的OIer,大牛请右上角。。

PS:若代码有误,请尽快与本人联系,我会尽快改正

首先引入一下splay的概念,他的中文名是伸展树,意思差不多就是可以随意翻转的二叉树

PS:百度百科中伸展树读作:BoGang,不知道是不是因为和某位大牛有关系

先看一道题目:

skydec有n个数,每次他都会把一些数放进一些盒子里,由于skydec太傻×,所以他不能判断数的大小,现在他请求你帮他求盒子里的第K小数

输入:一个数n表示数的个数,一个数m表示操作的个数 (n<=m<=100000)

操作由2部分组成,简称为a和b,如果a=0,则表示将b放进盒子里,如果a=1,则表示询问盒子里的第b小数

输出:对于每次询问输出答案

由于是临时造的题目,就不造样例了,引用一句RZZ大神的话:题是死的,人是活的,出题人是懒的。

如果是普通的查询的话,只要排序一遍直接输出data[b]。但是这是动态的

这里就要引进二叉搜索树了,二叉搜索树我就不介绍了,这都不知道的话也没必要来读这篇教程了= =。对于一个节点,我们可以从根开始,一直左右子树搜寻,直到搜到一个排名为k的数,具体实现:find(node,k)表示在以node为根的树中查找第k小的树。

当k=size[leftson[node]]+1时,显然根结点就是答案

然后分类讨论,当k<size[leftson[node]]+1时,往左子树搜寻,即返回find(leftson[node],k)

当k>size[leftson[node]]时,往右子树搜寻,即返回find(rightson[node],k-1-size[leftson[node]])

至于插入过程,我就不说了,太简单了

long find(long x,long k)
{
     long leftsize=size[leftson[x]];
     if(k==leftsize+1)return x;
     if(k<leftsize+1) return find(leftson[x],k);
     else             return find(rightson[x],k-leftsize-1);
}

如果是随机数据的话,这题随便无压力过,但是skydec开始丧心病狂了,他插入的数是递减的,这样的话这种方法就会导致时间复杂度O(n^2)

那么现在就要引进传说中的splay树了!

splay树是通过不断的旋转来维持logn的平均复杂度的,他不用像AVL树严格平衡,也不用记录高度等平衡信息,是十分灵活的,唯一的缺点就是常数比其他平衡树要大一点

但是我初学时一直有一个疑问:splay是如何维持logn的复杂度的呢

由于splay是不断翻转的,所以就算某一时刻他成了一条链,也会马上变成另外的形态,通过这样不断地变换可以防止长期停留在链的状态

打个比方,如果你有100元,你可以在某个彩票上孤注一掷,也可以买50张彩票,由于孤注一掷可能会跪掉,所以肯定是买50张好,因为就算跪了,也不会赔太多钱

然后平均时间就是logn了

现在引入旋转这个概念

其中,我们是以X结点为主角的

现在来解释一下这一幅图

解释之前,肯定有人要问了:这旋转有个鸟用。其实这次旋转成功地将结点X上提了一步,其实splay的最终目标就是把某个结点提到他的某个祖先的下面。

右旋其实只需要三步:

1.将X的右子树B(如果有的话)作为Y的左子树,同时让B认Y作爹

2.设Z为原本Y结点的父亲,让X认Z做爹(如果Z存在的话),将X作为Z的儿子(是左是右得由Y是Z的左儿子还是右儿子决定,要左右一致)

3.将Y作为X的右子树,同时让Y认X作爹

下面贴个代码吧,希望看得懂

void right_rotate(long x)
{
     long y=father[x];long z=father[y];
     leftson[y]=rightson[x];
     if(rightson[x]!=0)father[rightson[x]]=y;
     //对图中B子树,即X的右子树的处理
     father[x]=z;
     if(z!=0)
     {
             if(leftson[z]==y)leftson[z]=x;else rightson[z]=x;
             }
     rightson[x]=y;father[y]=x;
}


左旋同理,为了节省时间,就不多解释了,直接贴代码吧

void left_rotate(long x)
{
     long y=father[x];long z=father[y];
     rightson[y]=leftson[x];
     if(leftson[x]!=0)father[leftson[x]]=y;
     father[x]=z;
     if(z!=0)
     {
             if(leftson[z]==y)leftson[z]=x;else rightson[z]=x;
             }
     leftson[x]=y;father[y]=x;
}

下面来一个合集

O(∩_∩)O~请允许我写一个装13的代码

void rotate(long x,int kind)
//kind=0表示左旋,kind=1表示右旋 ch[X][0..1]表示X的左儿子和右儿子
{
     long y=father[x];long z=father[y];
     ch[y][!kind]=ch[x][kind];if(!ch[x][kind])father[ch[x][kind]]=y;
     father[x]=z;if(!z)ch[z][ch[z][1]==y]=x;
     father[y]=x;ch[x][kind]=y;
} 
那么现在,最基础的左旋右旋解决了

开始splay树最最最最最核心的操作----------splay

splay(x,Ancestry)表示将x不停向上旋转,知道X成为结点Ancestry的子树 (Ancestry意为祖先,装13专用)

一般应用比较广泛的是将X结点Splay到根节点

下面放一下伪代码

void splay(long x,long Ancestry)
{
     while(father[x]!=Ancestry)
     {
       各种旋转(~ o ~)~zZ
                               } 
} 

众人:(啪!)你特么不是写了跟没写一样么

好吧,下面开始补全各种旋转

旋转分为2种情况,其中两种情况又总共分为6种情况

第一种情况,Ancestry是X的父亲的父亲(可以称作爷爷了。。)

然后又分为两种情况:

1.

其中有些结点我懒得话了,比如X的右兄弟

这种情况下,只需要执行一遍 right_rotate(x)即可

2.

这种情况下,只需要执行一遍left_rotate(x)

//-------------------------------------------------------------------------------------------------------------------------------//

以上两种都是Ancestry是X的爷爷的情况

下面两种是Ancestry还不是X的爷爷的情况

3.

当leftson[z]=y且rightson[y]=x时,只需要执行两部:left_rotate(x),   right_rotate(x)

4.

当rightson[z]=y且leftson[y]=x时,只需要:right_rotate(x),left_rotate(x)即可

五六两种情况合起来讨论:


当形成类似一条线时

左边的情况:right_rotate(y),right_rotate(x);

右边的情况:left_rotate(y);left_rotate(x);

至于为什么要先旋转Y结点,我也不知道

那么到此为止,6种旋转都介绍完了,下面贴一波代码

void splay(long x,long Ancestry)
{
     while(father[x]!=Ancestry)
     {
         long y=father[x];long z=father[y];
         if(z==Ancestry)
         {
                        if(rightson[y]==x)left_rotate(x);else right_rotate(x);
                        }
         else
         {
                 if(rightson[z]==y&&rightson[y]==x){left_rotate(y);left_rotate(x);}
            else if(rightson[z]==y&&leftson[y]==x) {right_rotate(x);left_rotate(x);}
            else if(leftson[z]==y&&leftson[y]==x)  {right_rotate(y);right_rotate(x);}
            else                                   {left_rotate(x);right_rotate(x);}
            }
            }
      if(Ancestry==0)root=x;
} 

然后下面来一个装13版

void splay(long x,long Ancestry)
{
     while(father[x]!=Ancestry)
     {
                               long y=father[x];long z=father[y];
                               if(z==Ancestry)rotate(x,ch[y][0]==x);
                               else
                               {
                                   if(ch[z][0]==y)
                                   {
                                                  if(ch[y][0]==x)rotate(y,1),rotate(x,1);
                                                  else           rotate(x,0),rotate(x,1);
                                                  }
                                   else
                                   {
                                                  if(ch[y][1]==x)rotate(y,0),rotate(x,0);
                                                  else           rotate(x,1),rotate(x,0);
                                                  }
                                                  }
                                                  }
     if(Ancestry==0)root=x;
}


好吧,基础的一些操作的讲完了,下面大家肯定都想跃跃欲试了吧,那么我就来几道较简单的题目吧

1588: [HNOI2002]营业额统计

Time Limit: 5 Sec   Memory Limit: 162 MB
Submit: 6923   Solved: 2286
[ Submit][ Status]

Description

营业额统计 Tiger最近被公司升任为营业部经理,他上任后接受公司交给的第一项任务便是统计并分析公司成立以来的营业情况。 Tiger拿出了公司的账本,账本上记录了公司成立以来每天的营业额。分析营业情况是一项相当复杂的工作。由于节假日,大减价或者是其他情况的时候,营业额会出现一定的波动,当然一定的波动是能够接受的,但是在某些时候营业额突变得很高或是很低,这就证明公司此时的经营状况出现了问题。经济管理学上定义了一种最小波动值来衡量这种情况: 该天的最小波动值 当最小波动值越大时,就说明营业情况越不稳定。 而分析整个公司的从成立到现在营业情况是否稳定,只需要把每一天的最小波动值加起来就可以了。你的任务就是编写一个程序帮助Tiger来计算这一个值。 第一天的最小波动值为第一天的营业额。  输入输出要求

Input

第一行为正整数 ,表示该公司从成立一直到现在的天数,接下来的n行每行有一个正整数 ,表示第i天公司的营业额。

Output

输出文件仅有一个正整数,即Sigma(每天最小的波动值) 。结果小于2^31 。

Sample Input

6
5
1
2
5
4
6

Sample Output

12

HINT

结果说明:5+|1-5|+|2-1|+|5-5|+|4-5|+|6-5|=5+4+1+0+1+1=12

Source

这题除了数据坑爹以外,其他还可以 {数据有残缺,残缺的地方用0补上就行了}

请读者思考5分钟后再往下看

请思考~

请思考~

请思考~

请思考~

请思考~

请思考~

请思考~

请思考~

请思考~

请思考~

请思考~

请思考~

请思考~

请思考~

请思考~

请思考~

请思考~

请思考~

请思考~

请思考~

请思考~

请思考~

请思考~

好了五分钟到了!

下面分析一下题目

题目大概意思:每次插入一个数,在数列中找到一个数,使得该数与插入的数的差值最小

具体做法:我们可以按照大小建立一颗伸展树,然后每插入一个数,寻找他的前驱的后继

前驱寻找方法:寻找该结点左子树中最大的数,一颗树中最大的结点就是这颗树最右边的结点

long findpre(long x)
{
     long left=leftson[x];
     while(rightson[left]!=0)left=rightson[left];
     return left;
}

后继也同理,寻找该结点右子树中最小的数

long findsuc(long x)
{
     long right=rightson[x];
     while(leftson[right]!=0)right=leftson[right];
     return right;
}

最后比较一下前驱和后继即可

下面贴个完整代码

#include<stdio.h>
using namespace std;
struct node{long father,left,right,data;}tree[100008];
long time=0,n,root,sum=0;bool flag;
long MINS(long a,long b)
{
     if(a<b)return a;else return b;
}
long abs(long a)
{
     if(a<0)return -a;else return a;
}
void rightrotate(long x)
{
     long y=tree[x].father;long z=tree[y].father;
     tree[y].left=tree[x].right;
     if(tree[x].right!=-1){tree[tree[x].right].father=y;}
     tree[x].father=z;
     if(z!=-1){if(tree[z].left==y)tree[z].left=x;else tree[z].right=x;}
     tree[x].right=y;tree[y].father=x;
}
void leftrotate(long x)
{
     long y=tree[x].father;long z=tree[y].father;
     tree[y].right=tree[x].left;
     if(tree[x].left!=-1){tree[tree[x].left].father=y;}
     tree[x].father=z;
     if(z!=-1){if(tree[z].left==y)tree[z].left=x;else tree[z].right=x;}
     tree[x].left=y;tree[y].father=x;
}
void splay(long x)
{
     while(tree[x].father!=-1)
{
     long y=tree[x].father;long z=tree[y].father;
     if(z==-1){
               if(tree[y].left==x)rightrotate(x);else leftrotate(x);
               }
     else
     {
         if(tree[z].left==y&&tree[y].left==x){rightrotate(y);rightrotate(x);}
     else if(tree[z].left==y&&tree[y].right==x){leftrotate(x);rightrotate(x);}
     else if(tree[z].right==y&&tree[y].right==x){leftrotate(y);leftrotate(x);}
     else {rightrotate(x);leftrotate(x);}
     }}
     root=x;
}
long qq(long x)
{
     long y=tree[x].left;if(y==-1)return y;
     while(tree[y].right!=-1)y=tree[y].right;
     return y;
}
long long hj(long x)
{
     long y=tree[x].right;if(y==-1)return y;
     while(tree[y].left!=-1)y=tree[y].left;
     return y;
}
int  BST_insert(long dat,long x)
{
     if(dat==tree[x].data){flag=false;splay(x);return 0;}
     if(dat<tree[x].data)
     {
                         if(tree[x].left==-1){tree[x].left=time;tree[time].father=x;tree[time].left=tree[time].right=-1;tree[time].data=dat;}
                         else BST_insert(dat,tree[x].left);
                         }
     else
     {
        if(tree[x].right==-1){tree[x].right=time;tree[time].father=x;tree[time].left=tree[time].right=-1;tree[time].data=dat;}
                         else BST_insert(dat,tree[x].right);
                         }
}
int insert(long dat)
{
     flag=true;time++;
     BST_insert(dat,root);
     if(flag==false) return 0;
     splay(time);
     long q=qq(time);long h=hj(time);
     long min=2000000000;
     if(q!=-1)min=MINS(min,abs(tree[q].data-dat));
     if(h!=-1)min=MINS(min,abs(tree[h].data-dat));
     sum+=min;
}

int main()
{
    //freopen("test.in","r",stdin);freopen("testWA.out","w",stdout);
    long n,a1;
    while(scanf("%d",&n)!=-1){
                              sum=0;time=0;
                              scanf("%ld",&a1);
                              sum=a1;time++;tree[time].father=-1;
                              tree[time].left=tree[time].right=-1;
                              tree[time].data=a1;root=time;
    for(long i=1;i<=n-1;i++)
    {
             long dats=0;scanf("%ld",&dats);insert(dats);
             }
    printf("%ld\n",sum);}
    return 0;
}

大哭刚开始被数据坑了好多次,输入前变量置为0即可,这样即使读入失败还是0,不然跳出一堆2816947219840912

1208: [HNOI2004]宠物收养所

Time Limit: 10 Sec   Memory Limit: 162 MB
Submit: 3501   Solved: 1327
[ Submit][ Status]

Description

最近,阿Q开了一间宠物收养所。收养所提供两种服务:收养被主人遗弃的宠物和让新的主人领养这些宠物。每个领养者都希望领养到自己满意的宠物,阿Q根据领养者的要求通过他自己发明的一个特殊的公式,得出该领养者希望领养的宠物的特点值a(a是一个正整数,a<2^31),而他也给每个处在收养所的宠物一个特点值。这样他就能够很方便的处理整个领养宠物的过程了,宠物收养所总是会有两种情况发生:被遗弃的宠物过多或者是想要收养宠物的人太多,而宠物太少。 1. 被遗弃的宠物过多时,假若到来一个领养者,这个领养者希望领养的宠物的特点值为a,那么它将会领养一只目前未被领养的宠物中特点值最接近a的一只宠物。(任何两只宠物的特点值都不可能是相同的,任何两个领养者的希望领养宠物的特点值也不可能是一样的)如果有两只满足要求的宠物,即存在两只宠物他们的特点值分别为a-b和a+b,那么领养者将会领养特点值为a-b的那只宠物。 2. 收养宠物的人过多,假若到来一只被收养的宠物,那么哪个领养者能够领养它呢?能够领养它的领养者,是那个希望被领养宠物的特点值最接近该宠物特点值的领养者,如果该宠物的特点值为a,存在两个领养者他们希望领养宠物的特点值分别为a-b和a+b,那么特点值为a-b的那个领养者将成功领养该宠物。 一个领养者领养了一个特点值为a的宠物,而它本身希望领养的宠物的特点值为b,那么这个领养者的不满意程度为abs(a-b)。【任务描述】你得到了一年当中,领养者和被收养宠物到来收养所的情况,希望你计算所有收养了宠物的领养者的不满意程度的总和。这一年初始时,收养所里面既没有宠物,也没有领养者。

Input

第一行为一个正整数n,n<=80000,表示一年当中来到收养所的宠物和领养者的总数。接下来的n行,按到来时间的先后顺序描述了一年当中来到收养所的宠物和领养者的情况。每行有两个正整数a, b,其中a=0表示宠物,a=1表示领养者,b表示宠物的特点值或是领养者希望领养宠物的特点值。(同一时间呆在收养所中的,要么全是宠物,要么全是领养者,这些宠物和领养者的个数不会超过10000个)

Output

仅有一个正整数,表示一年当中所有收养了宠物的领养者的不满意程度的总和mod 1000000以后的结果。

Sample Input

5
0 2
0 4
1 3
1 2
1 5

Sample Output

3
(abs(3-2) + abs(2-4)=3,最后一个领养者没有宠物可以领养)

HINT

Source

这题。。跟上一题基本没区别,也是求前驱后继。

对于一颗树,他多加了一个属性:是宠物树还是顾客树

如果读入一个顾客,而当前是宠物树,设顾客为X,插入X,寻找X的前驱和后继,选一个与X差值最小的,将他与X一起删除,累加答案

如果当前是顾客树,直接将该顾客插入

如果是空树,属性改为顾客树,插入顾客

宠物也同理

但是这题多了一个删除操作!

现在来讲一下删除这个操作

首先,为了方便,我们将待删除点X提到根结点上来

然后寻找X的左子树中的最大值,将他提到X的左儿子(即图中的红点)

然后X的右子树=X的左儿子的右儿子

下面画个图来帮助理解


实现代码:

void del(long x)
{
     //整颗树中就X一个节点了,变成了空树 
     splay(x);if(ch[x][0]==-1&&ch[x][1]==-1){root=-1;return;}
     //没有左子树 
     if(ch[x][0]==-1){root=ch[x][1];fa[ch[x][1]]=-1;return;}
     //没有右子树 
     if(ch[x][1]==-1){root=ch[x][0];fa[ch[x][0]]=-1;return;}
     //左右子树俱全 
     long j=ch[x][0];while(ch[j][1]!=-1)j=ch[j][1];fa[ch[x][0]]=-1;splay(j);ch[j][1]=ch[x][1];fa[ch[x][1]]=j;root=j; 
}
下面贴一下代码~~~

#include<stdio.h>
#define MAXN 200000
using namespace std;
long ch[MAXN][2],fa[MAXN],data[MAXN];
long root,tot;long long sum=0;long n;
void rotate(long x,int kind)//kind=0 is leftrotate.kind=1 is rightrotate
{
     long y=fa[x];long z=fa[y];
     ch[y][1-kind]=ch[x][kind];if(ch[x][kind]!=-1)fa[ch[x][kind]]=y;
     fa[x]=z;if(z!=-1)ch[z][ch[z][1]==y]=x;fa[y]=x;ch[x][kind]=y;
}
void splay(long x)
{
     while(fa[x]!=-1)
     {
                     long y=fa[x];long z=fa[y];
                     if(z==-1)rotate(x,ch[y][1]!=x);
                     else
                     {
                         if(ch[z][0]==y)
                         {
                                        if(ch[y][0]==x){rotate(y,1);rotate(x,1);}else{rotate(x,0);rotate(x,1);}
                                        }
                         else
                         {
                                        if(ch[y][0]==x){rotate(x,1);rotate(x,0);}else{rotate(y,0);rotate(x,0);}
                                        }
                                        }} 
     root=x;
}
void newnode(long da,long fat)
{
     tot++;data[tot]=da;fa[tot]=fat;ch[tot][0]=ch[tot][1]=-1;
}
void BST_insert(long dat)
{
     if(root==-1){newnode(dat,-1);root=tot;return;}long now=root;
     while(true)
     {
          if(dat>data[now])
          {
                           if(ch[now][1]==-1){newnode(dat,now);ch[now][1]=tot;return;}
                           else now=ch[now][1];
                           }
          else
          {
                           if(ch[now][0]==-1){newnode(dat,now);ch[now][0]=tot;return;}
                           else now=ch[now][0];
                           }
                           }
}
void del(long x)
{
     //整颗树中就X一个节点了,变成了空树 
     splay(x);if(ch[x][0]==-1&&ch[x][1]==-1){root=-1;return;}
     //没有左子树 
     if(ch[x][0]==-1){root=ch[x][1];fa[ch[x][1]]=-1;return;}
     //没有右子树 
     if(ch[x][1]==-1){root=ch[x][0];fa[ch[x][0]]=-1;return;}
     //左右子树俱全 
     long j=ch[x][0];while(ch[j][1]!=-1)j=ch[j][1];fa[ch[x][0]]=-1;splay(j);ch[j][1]=ch[x][1];fa[ch[x][1]]=j;root=j; 
}
long prenum,pre,suc,sucnum;
void find_pre(long dat)
{
     long now=root;pre=-1;prenum=2100000000;
     while(true)
     {
                if(now==-1)return;
                if(data[now]<dat&&(dat-data[now])<prenum){pre=now;prenum=dat-data[now];}
                if(data[now]<dat)now=ch[now][1];else now=ch[now][0];
                }
}
void find_suc(long dat)
{
     long now=root;suc=-1;sucnum=2100000000;
     while(true)
     {
                if(now==-1)return;
                if(data[now]>dat&&(data[now]-dat)<sucnum){suc=now;sucnum=data[now]-dat;}
                if(data[now]<dat)now=ch[now][1];else now=ch[now][0];
                }
}
int main()
{
    long first_data,kind;scanf("%ld",&n);scanf("%ld%ld",&kind,&first_data);tot=0;newnode(first_data,-1);root=tot;sum=0;
    for(long i=2;i<=n;i++)
    {
             long k,dat;scanf("%ld%ld",&k,&dat);
             if(root==-1)
             {
                         kind=k;newnode(dat,-1);root=tot;}
             else
             {
                 if(k==kind){BST_insert(dat);splay(tot);}
                 else
                 {
                     find_pre(dat);find_suc(dat);
                     if(pre!=-1||suc!=-1)
                     {
                                         long mins=2100000001;long num=0;
                                         if(pre!=-1&&prenum<mins)mins=prenum,num=pre;
                                         if(suc!=-1&&sucnum<mins)mins=sucnum,num=suc;
                                         sum=(sum+mins)%1000000;del(num);//printf("%ld\n",mins);
                                         }
                                         }
                                         }
                                         }
     printf("%lld",sum);
     //for(;;);
     return 0;
}

1861: [Zjoi2006]Book 书架

Time Limit: 4 Sec   Memory Limit: 64 MB
Submit: 414   Solved: 251
[ Submit][ Status]

Description

小T有一个很大的书柜。这个书柜的构造有些独特,即书柜里的书是从上至下堆放成一列。她用1到n的正整数给每本书都编了号。 小T在看书的时候,每次取出一本书,看完后放回书柜然后再拿下一本。由于这些书太有吸引力了,所以她看完后常常会忘记原来是放在书柜的什么位置。不过小T的记忆力是非常好的,所以每次放书的时候至少能够将那本书放在拿出来时的位置附近,比如说她拿的时候这本书上面有X本书,那么放回去时这本书上面就只可能有X-1、X或X+1本书。 当然也有特殊情况,比如在看书的时候突然电话响了或者有朋友来访。这时候粗心的小T会随手把书放在书柜里所有书的最上面或者最下面,然后转身离开。 久而久之,小T的书柜里的书的顺序就会越来越乱,找到特定的编号的书就变得越来越困难。于是她想请你帮她编写一个图书管理程序,处理她看书时的一些操作,以及回答她的两个提问:(1)编号为X的书在书柜的什么位置;(2)从上到下第i本书的编号是多少。

Input

第一行有两个数n,m,分别表示书的个数以及命令的条数;第二行为n个正整数:第i个数表示初始时从上至下第i个位置放置的书的编号;第三行到m+2行,每行一条命令。命令有5种形式: 1. Top S——表示把编号为S的书房在最上面。 2. Bottom S——表示把编号为S的书房在最下面。 3. Insert S T——T∈{-1,0,1},若编号为S的书上面有X本书,则这条命令表示把这本书放回去后它的上面有X+T本书; 4. Ask S——询问编号为S的书的上面目前有多少本书。 5. Query S——询问从上面数起的第S本书的编号。

Output

对于每一条Ask或Query语句你应该输出一行,一个数,代表询问的答案。

Sample Input

10 10
1 3 2 7 5 8 10 4 9 6
Query 3
Top 5
Ask 6
Bottom 3
Ask 3
Top 6
Insert 4 –1
Query 5
Query 2
Ask 2

Sample Output

2
9
9
7
5
3

数据范围
30%的数据,n,m < = 10000
100%的数据,n,m < = 80000

HINT

Source

对于这题,建立一个以顺序为关键字的伸展树

用一个pos[x]数组表示代表编号为X的书的结点的编号

Top S:删除结点pos[S],将S插入到位置为1的地方

Botton S:删除结点pos[S],将S插入到位置为N的地方

Insert S T:删除位置为第S+1的书,然后将该书插入到S+1+T的位置

Ask S:将pos[S]splay到根节点,统计左子树的结点数

Query S:输出排名为S的书。

其中最后一个操作得好好讲讲,输出排名为S的书,我们可以用类似于插入的方法来写

这题由于是按顺序来排的,所以还要记录子树的大小

关于按顺序排序的求排名为K的编号的的方法,本文开头的skydec的傻×题里已经有类似的讲解了

下面直接贴代码吧:

/**************************************************************
    Problem: 1861
    User: SKYDEC
    Language: C++
    Result: Accepted
    Time:1604 ms
    Memory:8608 kb
****************************************************************/
 
#include<stdio.h>
#define MAXN 300000
using namespace std;
long s[MAXN],f[MAXN],ch[MAXN][2],data[MAXN],root,tot,n,m;
void updata(long x)
{
    s[x]=1;if(ch[x][0]!=-1)s[x]+=s[ch[x][0]];if(ch[x][1]!=-1)s[x]+=s[ch[x][1]];
}
void rot(long x,int kind)
{
    long y=f[x];long z=f[y];
    ch[y][!kind]=ch[x][kind];if(ch[x][kind]!=-1)f[ch[x][kind]]=y;
    f[x]=z;if(z!=-1)ch[z][ch[z][1]==y]=x;
    f[y]=x;ch[x][kind]=y;updata(y);updata(x);
}
void splay(long x,long place)
{
    while(f[x]!=place)
    {
        long y=f[x];long z=f[y];
        if(z==-1||z==place)rot(x,ch[y][1]!=x);
        else
        {
                    if(ch[y][1]==x&&ch[z][1]==y)rot(y,0),rot(x,0);
            else    if(ch[y][1]==x&&ch[z][0]==y)rot(x,0),rot(x,1);
            else    if(ch[y][0]==x&&ch[z][0]==y)rot(y,1),rot(x,1);
            else    rot(x,1),rot(x,0);
        }
    }
    if(place==-1)root=x;
}
void newnode(long fa,long dat){tot++;f[tot]=fa;s[tot]=1;ch[tot][0]=ch[tot][1]=-1;data[tot]=dat;}
long getsize(long x){if(x==-1)return 0;else return s[x];}
void insert(long x,long dat,long k)
{
    if(k==1&&ch[x][0]==-1){newnode(x,dat);ch[x][0]=tot;return;}
    if(k==2+getsize(ch[x][0])&&ch[x][1]==-1){newnode(x,dat);ch[x][1]=tot;return;}
    if(k<=1+getsize(ch[x][0]))insert(ch[x][0],dat,k);
    else insert(ch[x][1],dat,k-1-getsize(ch[x][0]));
    updata(x);
}
void del(long x)
{
    splay(x,-1);
    if(ch[x][0]==-1&&ch[x][1]==-1){root=-1;return;}
    if(ch[x][0]==-1){root=ch[x][1];f[ch[x][1]]=-1;return;}
    if(ch[x][1]==-1){root=ch[x][0];f[ch[x][0]]=-1;return;}
    long j=ch[x][0];while(ch[j][1]!=-1)j=ch[j][1];
    splay(j,x);
    ch[j][1]=ch[x][1];
    f[ch[x][1]]=j;f[j]=-1;root=j;
    updata(j);
}
long find(long k)
{
    long p=k;
    long a=root;
    while(true)
    {
        //if(p==5)printf("finding:%ld %ld %ld\n",a,data[a],k);
        if(k==1+getsize(ch[a][0]))return a;
        if(k>1+getsize(ch[a][0]))k-=1+getsize(ch[a][0]),a=ch[a][1];
        else a=ch[a][0];
    }
}
long get(long x)
{
    splay(x,-1);
    return getsize(ch[x][0])+1;
}
void bltree(long x)
{
    if(x==-1)return;
    printf("bl:%ld data:%ld l:%ld r:%ld s:%ld\n",x,data[x],ch[x][0],ch[x][1],s[x]);
    bltree(ch[x][0]);bltree(ch[x][1]);
}
long pos[100000];long a[100000];
int main()
{
    scanf("%ld%ld",&n,&m);for(long i=1;i<=n;i++)scanf("%ld",&a[i]),pos[a[i]]=i;tot=0;root=-1;
    for(long i=1;i<=n;i++)
    {
        if(i==1)newnode(-1,a[i]),root=tot;
        else{insert(root,a[i],i);if(i%2==0)splay(tot,-1);}
    }
    for(long ip=1;ip<=m;ip++)
    {
        char p[100];long j,k,i;
        scanf("%s%ld",&p,&i);
        if(p[0]=='I')scanf("%ld",&j);
        if(p[0]=='T'){del(pos[i]);insert(root,i,1);pos[i]=tot;splay(tot,-1);}
        if(p[0]=='B'){del(pos[i]);insert(root,i,n);pos[i]=tot;splay(tot,-1);}
        if(p[0]=='I'){long u=get(pos[i]);u=u-1+j;del(pos[i]);insert(root,i,u+1);splay(tot,-1);pos[i]=tot;}
        if(p[0]=='A'){printf("%ld\n",get(pos[i])-1);}
        if(p[0]=='Q'){printf("%ld\n",data[find(i)]);}
        //if(p[0]=='S'){
        //  for(long i=1;i<=n;i++)printf("%ld ",pos[i]);
        //  bltree(root);}
    }
    //for(;;);
    return 0;
}


好了,那么就先讲到这边吧,至于区间翻转这种高端的操作,我这种蒟蒻实在不懂。。有兴趣的朋友可以加我的QQ:1245133096来一起探讨OI问题!


评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值