[ZJOI2006]书架
题目描述
小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
Solution:
我们首先可以将书堆当作一个序列,那么不难理解本题就是要在这条序列上进行以下五种操作:
1、将某一元素移到序列队首
2、将某一元素移到序列队尾
3、将某一元素与前趋或者后继置换
4、查找某一元素前的元素个数
5、查找某一位置的元素
熟悉平衡树的人应该很容易想到此题可以用Splay解决,因为我们知道Splay被称为“区间之王”,一般在区间上的变换和查找都可以用到Splay(splay本质上是特殊的二分查找树),于是我们思考如何用Splay来解决这五种操作。
因为Splay的性质:特殊的二分查找树(Splay具有有序性——YALI 朱全明),所以旋转不会改变序列顺序,那么容易想到,题面中的五种操作可以这样实现:
1、 将某元素置顶:将元素旋到根,然后将左子树合并到该元素的后继
2、 将某元素置底:将元素旋到根,然后将右子树合并到该元素的前驱
3、 将某元素提前/滞后1位:直接与该元素的前趋/后继交换位置及信息
4、 询问指定元素排名:将元素旋到根,输出size-1
5、 询问指定排名元素:在树上find
解释一下实现:
这五种操作就是Splay板子,一般来讲Splay的基本操作有7种:左右旋、伸展、查找、插入和删除、求极值、求前趋后继、合并和分离。
1:插入节点
分几种情况:
如果现在树中没有节点那么加入这个新节点并且把它当做根
如果这个节点在树中已经出现过那么只要把对应节点的记录出现次数的值和以那个节点为根可以形成的子树大小+1就可以了
如果这个节点在树中没有出现过我们对树进行一次search(也就是二叉搜索树的search),然后把这个节点当做search返回值的儿子就可以了
2:求最大/最小值
就是search(maxlongint)(最大值)或者是search(-maxlongint)(最小值)的返回值嘛
3:删除
假设我们现在要删除x节点,那么怎么样才能在删除x节点后还使得树保证条件呢?
可以先把x节点splay一下,然后在x的左子树中找一个最大的点,splay一下这个点,然后把这个点当做新的根就可以了(这个点显然一开始是没有右儿子的,所以可行)
4:查询x数的排名
我们先search一下x这个数找到它在树中的具体位置(比如说是a节点),然后对a点splay,左/右子树的大小+1即是其从小到大/从大到小排名
5:查询排名为x的数
如果查询的是排名第x小的,那么我们从根节点开始,如果当前的x∈[a左儿子子树大小+1,a左子树大小+a节点的value值(也就是这个权值出现的次数)],那么第x小的数就是a节点对应的那个数
如果x< a左儿子子树大小+1,那么就是说排名第x小的点位置在a节点左儿子为根的那颗子树中,那么我们继续往左子树方向找
如果x> a左儿子子树大小+a节点的value值,那么第x小的位置就在a节点右儿子为根的那颗子树中,我们先把x-(a左儿子子树大小+a节点的value值)后往右儿子的方向找
最后把a splay一下(别问我为什么我也不知道~)
6:查询a(数值)的前驱/后继
首先找到a对应的位置x,然后把x节点splay一下,前驱就是x左儿子里最大的那个,后继就是x右儿子里最小的那个
7至于什么合并和分离(说实话我也不知道说的是什么),反正我将其理解为区间合并和从序列中分离出一段区间,这算是区间上的操作了,可以去看下这篇博客
顺便提下,学会了上述操作可以去做下模板题:P3369 【模板】普通平衡树(Treap/SBT)
本题代码:
#include<bits/stdc++.h> #define ll long long #define il inline #define debug printf("%d %s\n",__LINE__,__FUNCTION__) using namespace std; const int N=100005; int n,m,sz,root=1,pos[N],size[N],tree[N][2],fa[N],v[N]; il int gi() { int a=0;char x=getchar();bool f=0; while((x<'0'||x>'9')&&x!='-')x=getchar(); if(x=='-')x=getchar(),f=1; while(x>='0'&&x<='9')a=a*10+x-48,x=getchar(); return f?-a:a; } il void update(int x) { size[x]=size[tree[x][0]]+size[tree[x][1]]+1; pos[v[tree[x][0]]]=tree[x][0],pos[v[tree[x][1]]]=tree[x][1]; //debug; } il void rotate(int &root,int x) { int y=fa[x],z=fa[y]; bool p,q; tree[y][0]==x?p=0:p=1;q=p^1; y==root?root=x:(tree[z][0]==y?tree[z][0]=x:tree[z][1]=x); fa[x]=z,fa[y]=x,fa[tree[x][q]]=y,tree[y][p]=tree[x][q],tree[x][q]=y; //debug; update(y);update(x); } il void splay(int &root,int x) { int y,z; while(x!=root){ y=fa[x];z=fa[y]; if(y!=root){((tree[z][0]==y)^(tree[y][0]==x))?rotate(root,x):rotate(root,y);} //debug; rotate(root,x); } //debug; pos[v[x]]=x; } il void add(int x) { v[++sz]=x;size[sz]=1,pos[x]=sz,tree[sz][0]=tree[sz][1]=0; if(sz>1){tree[sz-1][1]=sz,fa[sz]=sz-1;splay(root,sz);} //debug; } il int find(int x,int k) { int y=tree[x][0]; //debug; if(size[y]+1==k)return x; else if(size[y]>=k)return find(y,k); return find(tree[x][1],k-size[y]-1); } il void top(int x) { x=pos[x];splay(root,x); if(!tree[x][0])return; if(!tree[x][1])tree[x][1]=tree[x][0],tree[x][0]=0; else { int y=find(root,size[tree[x][0]]+2); fa[tree[root][0]]=y;tree[y][0]=tree[root][0];tree[root][0]=0; splay(root,y); } } il void bottom(int x) { x=pos[x];splay(root,x); if(!tree[x][1])return; if(!tree[x][0])tree[x][0]=tree[x][1],tree[x][1]=0; else { int y=find(root,size[tree[x][0]]); fa[tree[root][1]]=y;tree[y][1]=tree[root][1];tree[root][1]=0; splay(root,y); } } il void ins(int f,int x) { if(!f)return; splay(root,pos[x]); //debug; int y=find(root,f==1?size[tree[pos[x]][0]]+2:size[tree[pos[x]][0]]); int x1=v[y],x2=pos[x]; swap(pos[x],pos[x1]);swap(v[x2],v[y]); } int main() { n=gi(),m=gi(); int a,b;char s[10]; while(n--)a=gi(),add(a); while(m--) { scanf("%s",s); if(s[0]=='T')top(gi()); else if(s[0]=='B')bottom(gi()); else if(s[0]=='I'){ins(gi(),gi());} else if(s[0]=='A'){a=gi(),splay(root,pos[a]);printf("%d\n",size[tree[pos[a]][0]]);} else if(s[0]=='Q')printf("%d\n",v[find(root,gi())]); } return 0; }