洛谷P2596 [ZJOI2006]书架【splay】

题目描述

小T有一个很大的书柜。这个书柜的构造有些独特,即书柜里的书是从上至下堆放成一列。她用1到n的正整数给每本书都编了号。

小T在看书的时候,每次取出一本书,看完后放回书柜然后再拿下一本。由于这些书太有吸引力了,所以她看完后常常会忘记原来是放在书柜的什么位置。不过小T的记忆力是非常好的,所以每次放书的时候至少能够将那本书放在拿出来时的位置附近,比如说她拿的时候这本书上面有X本书,那么放回去时这本书上面就只可能有X-1、X或X+1本书。

当然也有特殊情况,比如在看书的时候突然电话响了或者有朋友来访。这时候粗心的小T会随手把书放在书柜里所有书的最上面或者最下面,然后转身离开。

久而久之,小T的书柜里的书的顺序就会越来越乱,找到特定的编号的书就变得越来越困难。于是她想请你帮她编写一个图书管理程序,处理她看书时的一些操作,以及回答她的两个提问:(1)编号为X的书在书柜的什么位置;(2)从上到下第i本书的编号是多少。

输入格式:

第一行有两个数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本书的编号。

输出格式:

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

输入样例

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

输出样例

2
9
9
7
5
3

说明

100%的数据,n,m <= 80000


题目分析:

Top
把指定元素编号旋转到根
1.若此时根节点无左子树,则直接返回

2.若此时根节点无右子树,则直接交换左右子树

3.左右子树均存在
找到当前序列中排名为size[ch[rt][0]+2的编号,设为y
(即找到当前根节点元素在序列中的的下一个元素编号,后继)
令根的左子树成为y的左子树(ch[y][0]=ch[rt][0])

Bottom
把指定元素编号旋转到根
1.若此时根节点无右子树,则直接返回

2.若此时根节点无左子树,则直接交换左右子树

3.左右子树均存在
找到当前序列中排名为size[ch[rt][0]的编号,设为y
(即找到当前根节点元素在序列中的的上一个元素编号,前驱)
令根的右子树成为y的右子树(ch[y][1]=ch[rt][1])

Insert
t=0时直接忽略操作
把指定元素编号旋转到根
若t=1,找到根节点后继,交换编号及元素信息
若t=-1,找到根节点前驱,交换编号及元素信息

Ask
把指定元素编号旋转到根
输出根节点左子树大小

Query
常规find操作


#include<iostream>
#include<vector>
#include<algorithm>
#include<queue>
#include<cstring>
#include<cstdio>
using namespace std;

int read()
{
    int f=1,x=0;
    char ss=getchar();
    while(ss<'0'||ss>'9'){if(ss=='-')f=-1;ss=getchar();}
    while(ss>='0'&&ss<='9'){x=x*10+ss-'0';ss=getchar();}
    return f*x;
}

void print(int x)
{
    if(x<0){putchar('-');x=-x;}
    if(x>9)print(x/10);
    putchar(x%10+'0');
}

int n,m,rt;
int v[100010],pos[100010],size[100010];
int ch[100010][2],fa[100010],sz;
char ss[20];

void update(int p)
{
    size[p]=size[ch[p][0]]+size[ch[p][1]]+1;
}

void rotate(int& p,int x) { 
    int y=fa[x],z=fa[y]; 
    int t=(ch[y][0]==x);
    if(y==p) p=x; 
    else if(ch[z][0]==y) ch[z][0]=x; 
    else ch[z][1]=x;  
    fa[y]=x; fa[ch[x][t]]=y; fa[x]=z;  
    ch[y][t^1]=ch[x][t]; ch[x][t]=y;   
    update(y);update(x);  
} 

void splay(int& p,int x) 
{   
    while(x!=p) 
    {
        int y=fa[x],z=fa[y];
        if(y!=p) 
        {
            if((ch[y][0]==x)^(ch[z][0]==y)) rotate(p,x);
            else rotate(p,y);
        }
        rotate(p,x);
    }
}

void ins(int x)
{
    v[++sz]=x; pos[x]=sz;
    size[sz]=1; ch[sz][0]=ch[sz][1]=0;
    if (sz>1)
    {
        ch[sz-1][1]=sz;fa[sz]=sz-1;
        splay(rt,sz);
    }
}

int find(int p,int k)
{
    int ss=size[ch[p][0]];
    if(ss+1==k) return p;
    else if(ss>=k) return find(ch[p][0],k);
    else return find(ch[p][1],k-ss-1); 
}

void top_bottom(int d)
{
    int x=read(),y; x=pos[x];
    splay(rt,x);//指定元素旋转到根
    if(!ch[x][d]) return;//第1种情况
    if(!ch[x][d^1]) {ch[x][d^1]=ch[x][d]; ch[x][d]=0; return;}//第2种情况

    if(d==0) y=find(rt,size[ch[x][0]]+2);//元素置顶,找到根的后继
    else y=find(rt,size[ch[x][0]]);//元素置底,找到根的前驱
    fa[ch[rt][d]]=y;//合并子树
    ch[y][d]=ch[rt][d];
    ch[rt][d]=0;
    splay(rt,y);//伸展保证复杂度
}

void change()
{
    int x=read(),d=read(),y; if(d==0) return;//d=0直接忽略
    splay(rt,pos[x]);//指定元素旋转到根
    if(d==1) y=find(rt,size[ch[pos[x]][0]]+2);//元素提前,找到其前驱
    else y=find(rt,size[ch[pos[x]][0]]);//元素置后,找到其后继
    int tv=v[y],tpos=pos[x];//交换信息
    swap(pos[x],pos[tv]);
    swap(v[tpos],v[y]);
}

int get()
{
    int x=read(); x=pos[x];
    splay(rt,x);
    return size[ch[x][0]];
}

int main()
{
    n=read();m=read();rt=1;
    for(int i=1;i<=n;++i)
    {
        int x=read();ins(x);
    }

    while(m--)
    {
        scanf("%s",&ss);
        if(ss[0]=='T') top_bottom(0);
        else if(ss[0]=='B') top_bottom(1);
        else if(ss[0]=='I') change();
        else if(ss[0]=='A') print( get() ),putchar('\n');
        else if(ss[0]=='Q') print( v[ find(rt,read()) ] ),putchar('\n');
    }

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值