本文为crash的《运用伸展树解决数列维护问题》的学习笔记
数列维护问题,我们常用两种手段一是线段树,但使用线段树有一定的局限性,所以我们可以用伸展树解决这类问题,并且可以实现更多的功能。splay的两种操作就不赘述了,直接贴出神牛的代码。
// node为结点类型,其中ch[0]表示左结点指针,ch[1]表示右结点指针// pre表示指向父亲的指针
voidRotate(node *x,intc) //旋转操作,c=0表示左旋,c=1表示右旋{
node *y = x->pre;
y->ch[! c] = x->ch[c];
if (x->ch[c] != Null) x->ch[c]->pre = y;x->pre = y->pre;
if(y->pre != Null)
if(y->pre->ch[0] == y) y->pre->ch[0] = x;elsey->pre->ch[1] = x;
x->ch[c] = y, y->pre = x;
if(y == root) root = x;// root表示整棵树的根结点}
voidSplay(node*x,node*f)//Splay操作,表示把结点x转到结点f的下面{
for( ; x->pre != f; )
if(x->pre->pre == f)//父结点的父亲即为f,执行单旋转
if(x->pre->ch[0] == x) Rotate(x, 1);elseRotate(x,0);
else
{
node *y = x->pre, *z = y->pre;if(z->ch[0] == y)
if(y->ch[0] == x)
Rotate(y, 1), Rotate(x,1);
else
Rotate(x, 0), Rotate(x,1);else
if(y->ch[1] == x)
Rotate(y, 0), Rotate(x,0);
else
Rotate(x, 1), Rotate(x,0);
}
我们还可以做一些线段树做不到的事,一是如果我们要在 a 后面插入一些数,那么我们先把这些插入的数建成一棵伸展树,我们可以利用分治法建立一棵完全平衡的二叉树,就是说每次把最中间的作为当前区间的根,然后左右递归处理,返回的时候进行维护。接着将 a 转到根,将 a 后面一个数对应的结点转到根结点的右边,最后将这棵新的子树挂到根右子结点的左子结点上。
删除操作类似,提取区间,直接删除。
用伸展树解决数列维护问题,可以支持两个线段树无法支持的操作:在某个位置插入一些数和删除一些连续的数。但是也带了更大的常数和更大的代码量。因此,在没有必要使用伸展树的时候,我们就不应该使用。不过有些问题看似线段树无法解决,然而对模型进行转化后,也能用线段树解决,所以做题的时候不要急着出算法,还是要分析一下问题的本质,选择最好最合适的方法解决。