顾名思义就是没有旋转操做的treap.
仍是很好打的.
毕竟旋转操做旋转上天.html
两个核心操做: split和merge函数
split是将一棵树分红两棵树的操做.
注意这里的要求是对于肯定的树,将其前k个点分红新树, 剩下的点变成另外一颗新树,所以可能出现多个切割的地方.
对于一个节点来讲,咱们必然只会处理它的一颗子树,所以用递归去找处理的子树就好了.
返回时对于每个点更新一下它被处理的那颗子树.
函数的返回值是两颗子树的根.
具体看代码吧学习
define pii pair<int,int> define mp make_pair pii split(int rt, int k) { //对于根为rt的树,将它前k个点裂成一棵树A,剩下的点成为树B if (!rt) return mp(0, 0); pii tmp; pushdown(rt); if (k > S[C[rt][0]]) { //处理右子树 tmp = split(C[rt][1], k - S[C[rt][0]] - 1); //tmp表示右子树分裂出来的两棵树(A,B), //其中左边(A)的是当前rt的新的右子树 C[rt][1] = tmp.first; pushup(rt); tmp.first = rt; //更新rt, 而后将rt做为一个新的左子树, //原右子树分裂出来的右边的新树(B)做为新的右子树, //将这颗新树返回 } else { //处理左子树 tmp = split(C[rt][0], k); C[rt][0] = tmp.second; pushup(rt); tmp.second = rt; } return tmp; }
merge便是将两颗树合并的操做, 注意这里合并的树(A,B)要求max_value(A) < min_value(B),这样把AB一左一右相接(即保证B的每个节点都在A的右边)便保证了权值的有序,咱们就只要维护堆的性质了.(显然split分出来的两颗树就知足这样的性质)ui
int merge(int ra, int rb) { //返回新树的根 if (!ra) return rb; if (!rb) return ra; //有一颗空树,直接合并 pushdown(ra); pushdown(rb); if (KEY[ra] < KEY[rb]) { //ra的key值较小,维护小根堆的话要放在上面 C[ra][1] = merge(C[ra][1], rb); //默认rb是接在右边的树,所以rb必然会接进ra的右子树中 pushup(ra); return ra; //不要忘记更新 } else { C[rb][0] = merge(ra, C[rb][0]); pushup(rb); return rb; } }
这里是题目.
单点操做基本都能靠merge+split完成.
好比这题只需加上splay中同样的getkth(找到第k个数), findkth(找到数A的位置),
那么:
insert=getkth(findkth+getkth)+split+merge
delete=getkth(findkth+getkth)+split+merge
单点插入删除也可用(merge)(split)完成.code
这里是题目.
其实和单点操做没什么区别...
区间的插入删除也是使用(merge)(split)完成.
删除好说,可是注意插入时须要咱们先建好一颗子树再merge.
因而又有了一个build函数.
咱们能够一个一个把点插到新树中去(一开始有一颗空树).
那么每次插入的点必然在树的最右端.htm
而后开始维护小根堆的性质.
考虑root -> right son -> right son ... 这样一条链, 咱们先把新点接在这条链最下面,
而后找到其中深度最小的一个key值大于大于点的节点,把以它为根的子树当作新点
的左子树, 而后用新点代替它原来的位置就能够了.(至关于把新点沿着链一直向上旋)
可是须要注意排布在这条链上的树是没有维护(pushup/update)过的, 所以每次
寻找到要被移到新点下面的点都须要一次pushup, 最后再给仍在链上的点来一发pushup.
由于每次加入的点都在链上, 能够证实每一个点都会(在它的全部子树以后)通过一次pushupblog
还有一个须要注意的点是splay中的虚点.
无旋treap并不须要虚点,可是在pushup的时候可能考虑到空子树的状况,为避免空子树的影响
须要一个初始化.递归