无旋treap(fhq)
之前写过一篇按权值的fhq,可以解决查询前驱后继,第k大等操作。
不过如果我们把fhq的特性转移到数列上,即类比线段树储存sum,标记之内,那么又是一个新毒瘤数据结构了。
补:(一些对FHQ的理解)
-
一共只有n个节点,不像线段树有2n个左右,因为fhq节点的左右儿子都是直接的节点(而线段树需要辅助节点),同时如果是对序数fhq,中序遍历就是当前数组,而对权值fhq,中序遍历是从小到大的顺序。
-
split的时候,两个取址的参数是分别两个分裂子树的root,注意,他们是完全分割的两个东西,并没有直接联系,第一层分裂完后,下一个该归到同一个分裂fhq的作为它的左右儿子。
一些注意事项:
-
FHQ的正确性基于满足中序遍历和堆性质的树的形态是固定的。
-
pushdown操作如果要改变子树形态或者siz大小,需要小从把标记推完后,再进行操作。
毒瘤板题传送门
我们知道线段树可以实现区间和,区间更改,最大子段和等操作,但此题还要求加入一串数、删除一串数、翻转一串数的操作。
现在思考一下如何用fhq解决以上问题。
-
fhq本身由插入顺序决定相对位置(因为其本身是一棵二叉搜索树,只不过这里把权定位下标而已),再由随记的rank保证时间复杂度
-
由fhq的毒瘤特性,我们可以两次split取出一段区间(对应在fhq上是一棵子树),从而方便进行加入,删除,翻转等涉及到序列结构及相对顺序的操作。
-
关于split一点小说明,我们提取的是两端区间的root元素,所以只要第一次发现大了或小了,就可以确定两个root,其余的递归更改的则是root的children及root的children的children…
剩下的就考验码力了
(p.s 垃圾回收真tm生动形象)
code
1 |
|
例题2
本题唯一的难点,是如何把找到编号对应的位置,因为我们知道要找的标号的节点,无法确定中序遍历是第几个。故此题我们要求出fa数组表示每个节点的父亲,只需要在split和merge时更改fa就行了。
然后在那个点不断沿着fa链上去,如果是右儿子,那么就ans就加上左子树size+1,因为中序遍历先走左儿子,如果是右儿子,并不会有贡献。
于是我们单独写个Find函数找到编号为s的节点是原序列第几个,然后就是split和merge的基本操作。
code
1 |
|
例题3
本题需要用平衡树维护线段树。当然还有更优秀的做法,不过树套树的做法更显然也更实用。
因为本题我们要随时查询一段区间出现超过一半的数,且带修。
首先我们想清楚,如果一段区间内有数超过了一半,那么我们把这段区间任意划分成两段,至少有一段超过一半。
于是我们依据这个结论可以用左右儿子出现的最大值更新父节点的值。
这个用线段树实现,但是如何快速判断一个数在一段区间内出现次数,我们对每个不同的数建一个fhq,于是split两次看所求区间的size是否大于一半即可。
code
1 |
|
例题4
我们考虑普通的dp转移方程,设dp[i]为以i结尾的最长不下降序列的长度,我们对于新加的点,需要在1->i-1中找出最大的dp[i]来更新dp(值为dp[i]+1)。
所以我们需要需要有两个操作,查询区间最大值,插入元素。用平衡树就行了。
注意一点:因为加数是按从小到大的顺序,所以新加的的数必定是原序列最大的数,并不会对新的pos之后的dp数组有影响,所以只用单点更新pos就行了(dp数组的下标不是序列顺序,是对应的数字,因为数字是唯一的,序列顺序会变化)
code
1 |
|