splay伸展树基础操作(bzoj 1861: [Zjoi2006]Book 书架)

splay:一种排序树(中序遍历权值有序)

主要性质:随着访问翻转次数的增多,复杂度越来越接近logn,形态也越来越接近平衡树

主要功能:每次将要询问or删除or修改的点先一路翻转到根,然后再满足所需操作

翻转规则如下:

其中

②or③+①or④的操作为Zig-Zag

①or④+①or④的操作为Zig-Zig

具体解析看代码

参考博文:http://blog.csdn.net/changtao381/article/details/8936765

 

1861: [Zjoi2006]Book 书架

Time Limit: 4 Sec  Memory Limit: 64 MB
Submit: 1677  Solved: 955
[Submit][Status][Discuss]

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

此题解析:

从上到下,从小到大

①②③操作:删掉对应节点并插入要求的位置

④操作:将对应节点旋转到根后求其左子树大小

⑤操作:直接从根开始Find()第S小的点

#include<stdio.h>
#include<string.h>
#define inf 1000000000
using namespace std;
int n, m, root, sz, tre[80005][2], fa[80005], deep[80005], a[80005], size[80005], v[80005], pos[80005];
void Update(int k)		//更新k节点的size值
{
	size[k] = size[tre[k][0]]+size[tre[k][1]]+1;
}
void Create(int l, int r, int last)			//传入的是当前区间以及其"父亲"
{
	int mid;
	if(l>r)
		return;
	mid = (l+r)/2;		//mid作为当前节点
	if(l==r)
	{
		v[mid] = a[mid];
		size[mid] = 1;
		fa[mid] = last;
		if(mid<last)  tre[last][0] = mid;		//判断当前的mid节点是last节点的左儿子还是右儿子并连接,下同
		else  tre[last][1] = mid;
		return;
	}
	Create(l, mid-1, mid);
	Create(mid+1, r, mid);
	v[mid] = a[mid];
	fa[mid] = last;
	Update(mid);
	if(mid<last)  tre[last][0] = mid;
	else  tre[last][1] = mid;
}
void Rotate(int x, int &k)
{
	int l, r, y, z;
	y = fa[x], z = fa[y];
	if(tre[y][0]==x)  l = 0;
	else  l = 1;
	r = l^1;
	if(y==k)
		k = x;
	else
	{
		if(tre[z][0]==y)  tre[z][0] = x;
		else  tre[z][1] = x;
	}
	fa[x] = z, fa[y] = x;
	fa[tre[x][r]] = y;
	tre[y][l] = tre[x][r];
	tre[x][r] = y;
	Update(y);
	Update(x);
}
void Splay(int x, int &k)			//将点x不停地向上旋,直到x代替节点k的位置
{
	int y, z;
	while(x!=k)
	{
		y = fa[x], z = fa[y];
		if(y!=k)
		{
			if((tre[y][0]==x)^(tre[z][0]==y))		//对于形式②或形式③	Zig-Zag
				Rotate(x, k);
			else
				Rotate(y, k);					//Zig-Zig
		}
		Rotate(x, k);
	}
}
int Find(int k, int rank)			//寻找排名rank的点
{
	int l, r;
	l = tre[k][0], r = tre[k][1];
	if(size[l]+1==rank)
		return k;
	else if(size[l]>=rank)
		return Find(l, rank);
	else
		return Find(r, rank-size[l]-1);
}
void Delete(int k)		//删除节点k
{
	int x, y;
	x = Find(root, k-1);		//找到比k小且离k最近的节点x(这个时候k已经被翻转到根了)
	y = Find(root, k+1);		//找到比k大且离k最近的节点y
	Splay(x, root);				//将x一路翻转到根
	Splay(y, tre[x][1]);			//y作为x的右节点
	tre[y][0] = 0;			//因为大小在x和y之间的节点只有k,而k已经被删了,所以y没有左子树,清空
	Update(y);
	Update(x);
}
void Move(int k, int val)			//val表示的只是操作的类型而已
{
	int x, y, z, rank;
	z = pos[k];
	Splay(z, root);
	rank = size[tre[z][0]]+1;
	Delete(rank);				//其实是个先删除在插入的过程
	if(val==inf)
	{
		x = Find(root, n);
		y = Find(root, n+1);
	}
	else if(val == -inf)
	{
		x = Find(root, 1);
		y = Find(root, 2);
	}
	else
	{
		x = Find(root, rank+val-1);
		y = Find(root, rank+val);
	}
	Splay(x, root);					//一样,将x翻转到根,将y作为x的右节点
	Splay(y, tre[x][1]);				//因为大小在x和y之间的只有待插入的值,所以将带插入值作为y的左子树
	size[z] = 1, fa[z] = y, tre[y][0] = z;
	Update(y);
	Update(x);
}
int main(void)
{
	int i, S, T;
	char str[15];
	scanf("%d%d", &n, &m);
	for(i=2;i<=n+1;i++)
	{
		scanf("%d", &a[i]);
		pos[a[i]] = i;
	}
	Create(1, n+2, 0);	//  在两端加入两个边界点
	root = (n+3)/2;
	while(m--)
	{
		scanf("%s%d", str+1, &S);
		switch(str[1])
		{
			case 'T':  Move(S, -inf);  break;		//把T放在最上面(让T的标号最小)
			case 'B':  Move(S, inf);  break;		//把T放在最下面(让T的标号最大)
			case 'I':  scanf("%d", &T);  Move(S, T);  break;
			case 'A':  Splay(pos[S], root);  printf("%d\n", size[tre[pos[S]][0]]-1);  break;		//把pos[S]节点旋到根,那么在S上面的个数显然是其左子树的size值(这里-1是因为加了两个边界点)
			case 'Q':  printf("%d\n",  v[Find(root, S+1)]);			//直接查找
		}
	}
	return 0;
}
原创文章 1134 获赞 1439 访问量 61万+

猜你喜欢

转载自blog.csdn.net/Jaihk662/article/details/75213675
今日推荐