伸展树的核心操作就是splay了,将每次访问的点splay到根节点(类似输入法,频繁打的词会比较靠前)。而正确的splay操作可以保证在将x旋转到根节点的过程中,沿途的所有父节点的深度都会至少减少一半,这种自我调整的特性保证了splay树在各种操作下均摊$O(\log n)$rotate需要分3种情况,splay需要分6种情况比较麻烦,不再赘述( 其实是不会 ),只给出代码实现:
1 | void rotate(int x) |
splay(x,g)表示将x旋转到g的儿子处,当g=0时即将x旋转到根。用splay来维护权值大小我觉得很麻烦,码量和常数都比较大,一般情况下用pbds或者fhqtreap替代即可。但是用splay来维护区间还是需要只要的。做法也很简单,如果要维护[l,r],先把l-1旋转到根,再把r+1旋转到l-1的儿子,那么此时r+1的左子树就是要操作的[l,r]部分,直接对这个根节点打上相应的标记即可:
1 | int split(int l, int r) |
pushdown操作只需要在kth函数中进行即可:
1 | int kth(int p, int k) |
而pushup操作在每次进行split区间操作后都要执行,假设split返回的节点是x,那么就要pushup(y)以及pushup(fa[y]):
1 | void replace(int l, int r, int v) |
其实splay的所有操作都可以用fhqtreap来替代,不过在区间操作中要找到区间下标x的点这一功能还是splay方便,只需要把这个点splay到根就可以了,而treap则需要额外维护父亲,而自下而上找。当然splay无可替代的地方就是LCT了,如果用treap就会多一个log。