学习用伸展树解决数列维护问题

             本文为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,b】区间,显然只要用splay的操作将树根变为a-1,将树根的右节点变为b+1,则该节点的左子树即表示区间【a,b】。这意味着我们可以实现线段树的一些功能,只要我们在每个节点上记录关于以这个节点为根的子树的信息。还可以对区间进行整体修改,可以用和线段树类似的延迟标记技术,就是对于每个结点,再额外记录一个或多个标记,表示以这个结点为根的子树是否被进行了某种操作,并且这种操作影响其子结点的信息值。当然,既然记录了标记,那么旋转和其他一些操作中也就要相应地将标记向下传递。

  我们还可以做一些线段树做不到的事,一是如果我们要在 a 后面插入一些数,那么我们先把这些插入的数建成一棵伸展树,我们可以利用分治法建立一棵完全平衡的二叉树,就是说每次把最中间的作为当前区间的根,然后左右递归处理,返回的时候进行维护。接着将 a 转到根,将 a 后面一个数对应的结点转到根结点的右边,最后将这棵新的子树挂到根右子结点的左子结点上。

删除操作类似,提取区间,直接删除。

  用伸展树解决数列维护问题,可以支持两个线段树无法支持的操作:在某个位置插入一些数和删除一些连续的数。但是也带了更大的常数和更大的代码量。因此,在没有必要使用伸展树的时候,我们就不应该使用。不过有些问题看似线段树无法解决,然而对模型进行转化后,也能用线段树解决,所以做题的时候不要急着出算法,还是要分析一下问题的本质,选择最好最合适的方法解决。



 
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值