数据结构 - Splay
简介
借助左旋和右旋操作,在保证 BST 性质的情况下尽可能地减少树的高度。
当结点 呈直线形的时候,;呈折线形时,。可以五个结点分情况看一下,这样能保证树的高度尽可能小。
文艺平衡树
维护一个 项有序数列,序列中第 项初始为 ,输入 行 表示把区间 翻转。
输出进行 次变换后的序列。
题目链接:AcWing 2437,P3391。
左旋和右旋可以合并到一个函数中写,在现在的 Splay 代码中大量存在着用布尔表达式判断是哪个子结点的情况,这样可以少写很多 if 语句。
1 |
|
普通平衡树
您需要写一种数据结构(可参考题目标题),来维护一些数,其中需要提供以下操作:
- 插入 x 数;
- 删除 x 数(若有多个相同的数,只删除一个);
- 查询 x 数的排名(若有多个相同的数,输出最小的排名);
- 查询排名为 x 的数;
- 求 x 的前驱(前驱定义为小于 x,且最大的数);
- 求 x 的后继(后继定义为大于 x,且最小的数)。
题目链接:AcWing 253,P3369。
这题比上面的文艺平衡树反而更难写,删除一个数实在是很困难的。
1 |
|
[HNOI2002] 营业额统计
给出一个 n 个数的数列,定义 ,其中 ,求
题目链接:LOJ 10143,AcWing 265。
本题坑点在于数列中有重复元素。
1 |
|
[NOI2004] 郁闷的出纳员
题干过长,这里不粘过来了。
输入格式
第一行有两个非负整数 n 和 min,n 表示下面有多少条命令,min 表示工资下界。
接下来的 n 行,每行表示一条命令,命令可以是以下四种之一:
I
命令,格式为I_k
,表示新建一个工资档案,初始工资为 k。如果某员工的初始工资低于工资下界,他将立刻离开公司。A
命令,格式为A_k
,表示把每位员工的工资加上 k。S
命令,格式为S_k
,表示把每位员工的工资扣除 k。F
命令,格式为F_k
,表示查询第 k 多的工资。
_
(下划线)表示一个空格,I
命令、A
命令、S
命令中的 k 是一个非负整数,F
命令中的 k 是一个正整数。在初始时,可以认为公司里一个员工也没有。
输出格式
输出行数为 F 命令的条数加一。对于每条 F 命令,你的程序要输出一行,仅包含一个整数,为当前工资第 k 多的员工所拿的工资数,如果 k 大于目前员工的数目,则输出 −1。
输出文件的最后一行包含一个整数,为离开公司的员工的总数。
注意,如果某个员工的初始工资低于最低工资标准,那么将不计入最后的答案内。
题目链接:AcWing 950,P1486。
本题不改变相对大小关系,因此可以让 Splay 保序。
1 |
|
[HNOI2012] 永无乡
永无乡包含 n 座岛,编号从 1 到 n ,每座岛都有自己的独一无二的重要度,按照重要度可以将这 n 座岛排名,名次用 1 到 n 来表示。
某些岛之间由巨大的桥连接,通过桥可以从一个岛到达另一个岛。
如果从岛 a 出发经过若干座(含 0 座)桥可以到达岛 b,则称岛 a 和岛 b 是连通的。
现在有两种操作:
B x y
表示在岛 x 与岛 y 之间修建一座新桥。Q x k
表示询问当前与岛 x 连通的所有岛中第 k 重要的是哪座岛,即所有与岛 x 连通的岛中重要度排名第 k 小的岛是哪座,请你输出那个岛的编号,如果不存在输出 -1。题目链接:AcWing 1063,P3224。
首先应该开多少数组大小,最坏情况下需要把所有岛都合并,合并时把小的合并到大的里面去,这样能尽可能避免浪费空间,于是我们注意到在最坏的情况下会将两个长度为 的连通块合并,而得到这两个连通块的方式最坏情况下也是 的连通块合并,这样递归下去。
可以写出下面的函数帮助我们得到一个需要的空间:
用 matplotlib 画了一下,惊奇地发现这是一条直线,于是继续展开,然后小小放缩了一下:
所以这可以近似的认为是直线 ,根据本题数据,保险点开 350000
就够了。
1 |
|
[NOI2005] 维护数列
请写一个程序,支持以下 6 种操作。
编号 名称 格式 说明 1 插入 在当前数列的第 个数字后插入 个数字,若在数列首插入,则 2 删除 从当前数列的第 个数字开始连续删除 个数字 3 修改 从当前数列的第 个数字开始的连续 个数字统一修改为 4 翻转 取出从当前数列的第 个数字开始的 个数字,翻转后放入原来的位置 5 求和 计算从当前数列的第 个数字开始的 个数字的和并输出 6 求最大子列和 求出当前数列中和最大的一段子列,并输出最大和 题目链接:AcWing 955,P2042。
当需要进行 Splay 操作之前,都会进行 get_kth
,此时 pushdown
已经进行过了,因此 Splay 操作是不需要 pushdown
的。
1 |
|
题目保证树中元素数量小于 ,那么实际上可以缩小十倍的内存,这就需要我们进行一个内存回收的操作,但代价是删除时需要 DFS 一遍,多了很多时间,所以如果空间不够用才考虑内存回收。
我一般静态变量初始化为 0 都不写,这个时候就成功地把我坑死了。
1 |
|