splay树入门级教程
【例1】
skydec有n个数,每次他都会把一些数放进盒子里,由于skydec太傻×,所以他不能判断数的大小,现在他请求你帮他求盒子里的第b小数
输入:一个数n表示数的个数,一个数m表示操作的个数 (n<=m<=100000)
操作由2部分组成,简称为a和b,如果a=0,则表示将b放进盒子里,如果a=1,则表示询问盒子里的第b小数
输出:对于每次询问输出答案
【算法】
如果是普通的查询的话,只要排序一遍直接输出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树的旋转:
1.右旋(zig)
这次旋转成功地将结点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;
}
2.左旋(Zag)
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;
}
左右旋转合起来写:
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的基础,对于二叉查找树来说,树的旋转不破坏查找树的结构。
Splaying
伸展(splay)是Splay Tree中的基本操作,就是对每次被查找、插入等操作的节点用上述方法旋转到根的位置,同时保证二叉排序树的性质不变。
Splaying的操作受以下三种因素影响:
· 节点x是父节点p的左孩子还是右孩子
· 节点p是不是根节点,如果不是
· 节点p是父节点g的左孩子还是右孩子
同时有三种基本操作:
Zig Step
当p为根节点时,进行zip step操作。
当x是p的左孩子时,对x右旋;
当x是p的右孩子时,对x左旋。
Zig-Zig Step
当p不是根节点,且x和p同为左孩子或右孩子时进行Zig-Zig操作。
当x和p同为左孩子时,依次将p和x右旋;
当x和p同为右孩子时,依次将p和x左旋。
Zig-Zag Step
当p不是根节点,且x和p不同为左孩子或右孩子时,进行Zig-Zag操作。
当p为左孩子,x为右孩子时,将x左旋后再右旋。
当p为右孩子,x为左孩子时,将x右旋后再左旋。
对应的具体操作有:
1.伸展操作(splay)
2.查找操作
3.插入操作
4.求极值操作
5.删除操作
6.求前驱后继
7.求第k极值
8.求x是第几大
应用
Splay Tree可以方便的解决一些区间问题,根据不同形状二叉树先序遍历结果不变的特性,可以将区间按顺序建二叉查找树。
每次自下而上的一套splay都可以将x移动到根节点的位置,利用这个特性,可以方便的利用Lazy的思想进行区间操作。
对于每个节点记录size,代表子树中节点的数目,这样就可以很方便地查找区间中的第k小或第k大元素。
对于一段要处理的区间[x, y],首先splay x-1到root,再splay y+1到root的右孩子,这时root的右孩子的左孩子对应子树就是整个区间。
这样,大部分区间问题都可以很方便的解决,操作同样也适用于一个或多个条目的添加或删除,和区间的移动。
代码参考“splay(伸展树)代码,自底向上实现.txt”
作业:
! http://www.lydsy.com/JudgeOnline/problem.php?id=3224
!http://www.lydsy.com/JudgeOnline/problem.php?id=3223
1.营业额统计(turnover)
(hn2002)
Tiger最近被公司升任为营业部经理,他上任后接受公司交给的第一项任务便是统计并分析公司成立以来的营业情况。
Tiger拿出了公司的账本,账本上记录了公司成立以来每天的营业额。分析营业情况是一项相当复杂的工作。由于节假日,大减价或者是其他情况的时候,营业额会出现一定的波动,当然一定的波动是能够接受的,但是在某些时候营业额突变得很高或是很低,这就证明公司此时的经营状况出现了问题。经济管理学上定义了一种最小波动值来衡量这种情况:
该天的最小波动值
当最小波动值越大时,就说明营业情况越不稳定。
而分析整个公司的从成立到现在营业情况是否稳定,只需要把每一天的最小波动值加起来就可以了。你的任务就是编写一个程序帮助Tiger来计算这一个值。
第一天的最小波动值为第一天的营业额。
l 输入输出要求
输入由文件’turnover.in’读入。
第一行为正整数,表示该公司从成立一直到现在的天数,接下来的n行每行有一个正整数,表示第i天公司的营业额。
输出到文件’turnover.out’。
输出文件仅有一个正整数,即。结果小于。
l 输入输出样例
Turnover.in | Turnover.out |
6 5 1 2 5 4 6 | 12 |
结果说明:5+|1-5|+|2-1|+|5-5|+|4-5|+|6-5|=5+4+1+0+1+1=12
2. NOI2005 sequence
平衡树:
noi2005 sequence
~ http://www.lydsy.com/JudgeOnline/problem.php?id=1269
~http://www.lydsy.com/JudgeOnline/problem.php?id=1208
~http://www.lydsy.com/JudgeOnline/problem.php?id=1503
*http://www.lydsy.com/JudgeOnline/problem.php?id=2209
* http://www.lydsy.com/JudgeOnline/problem.php?id=1500
*http://www.lydsy.com/JudgeOnline/problem.php?id=3600
*http://www.lydsy.com/JudgeOnline/problem.php?id=3196
spoj 7734. TWIST
spoj 8406.TEMPLEQ
ural 1469. No smoking!
pku 2828
pku 2892
pku 2985
pku 3481
poi 18 Tree Rotations(rot)
已知一棵二叉树,树的每个叶子节点上都有权值。任意交换一个内部节点的左右儿子,问按照dfs序写下所有叶子上的权值后的序列里,逆序对最少能有多少。
启发式合并平衡树。一边合并一边求在另一个平衡树里比当前插入的节点小的节点数。
CEOI 2006 queue
初始1~n的数列。有n个操作,把B放在紧接着A的位置。之后有q个询问:P X:询问x所在的位置;L X:询问x位置的数字。
HDU 4453 Looploop (伸展树splay tree)
HDU 3726 Graph and Queries (离线处理+splay tree)
POJ 3580 SuperMemo (splay tree)