从不依靠,从不寻找,非常沉默,非常骄傲。
Splay区间翻转
显然在Post 17Splay基本操作讲清楚了,又有如下几个问题
- 求x的前驱其实就是求x的左子树的最右边的一个结点,后继是求x的右子树的左边一个结点(想一想为什么?)
答案非常简单,如果我们把x先插入旋转到根上,左子树全部不大于x,右子树全部不小于x,左子树最右边节点保证在左子树内最大,右子树最左边节点同理。不大于x的最大值与不小于x的最小值就是前驱后继的定义(不过有可能重复)。
- 为什么Splay提取一个区间是将区间左端点的前驱旋转至根,右端点后继旋转至根的右儿子,右儿子的左子树就是区间呢?
这个问题非常重要,是区间操作的基本原理。
显然第一次旋转后左端点在根的右子树,第二次旋转后右端点在根的右儿子的左子树,在区间(L,R)内,任何一个节点既不能在根的左子树(BST性质,它们大于左端点),又不能在根的右儿子的右子树(同样不能大于右端点),因此整个闭区间所有节点都在根的右儿子的左子树内了!
- 还有个我认为非常难证明的问题,就是为什么懒标记是正确的。。。线段树懒标记正确在于它的形态是不会发生改变的,显然每个节点代表了固定的区间。但是Splay显然作为子树的根的节点只是临时的,那会不会造成错误的标记呢?
这个问题让我想了半天。实际上是这样的。考虑暴力Splay翻转区间是从根的右儿子的左儿子u开始,对于每个节点我们都交换左右子树,这相当于一组操作。显然只要我们没有访问一个点,它的子树交不交换并没有什么区别。也就是说我们没必要重复交换,只需要在访问的时候确保进行操作并下传标记即可。那么对于当前根,我们交换子树并给这个点打上标记,后面只要访问到这个点就下推标记并操作,这样确保每个该翻转的子树翻转了,也能优化复杂度(具体严格复杂度证明以后补上吧)。
总结一下这个问题就是:只要子树不旋转,这个子树的根就不pushdown,这其实就是懒标记的思想,尽管Splay不像线段树形态固定,只要遵循这一原则就一定不会出错。
上述问题都理解了的话,一遍写过Splay十分轻松(97行)
那么区间操作也不是很难了,我们只需要在提取的区间根节点交换左右儿子并打上懒标记即可。
献上我自己靠理解写的小常数Splay (⊙o⊙)。
Code:
1 |
|
接下来就继续学习进阶指南了。
感觉动力有点不足