势能线段树【2021东北四省D(HDU7116)Lowbit】【2021杭电多校8/HDU7059 Counting Stars】
势能线段树
简言之,有的区间修改不支持lazy
标记(如区间开根号、取模、加减lowbit
),但是这些修改存在一些奇妙的性质,即每个元素的修改次数有一个上限。我们在每个节点上记录一个值,表示区间内元素是否达到修改上限。在修改时,若没有达到上限,则暴力递归到叶子节点。反之,使用lazy
标记或者直接return
。
具体来说,势能线段树是指在懒标记无法正常使用的情况下(如区间开根号),暴力到叶子将线段树当成数组一样用进行修改。这个时候的复杂度计算就不是很直观了,所以引入势能的概念。每一个节点都有一个关于开根号操作的“势能”,然后开根号的时候势能一定是递减的。注意引入势能分析是忽略过程,只考虑所有过程的总和。即我们不管在一次暴力中到底一个树节点被访问了几次(单次操作的时间复杂度可能很大,可能是O(n)
)但是这些暴力的总量是势能上限。
一.2021东北四省D(HDU7116)Lowbit
题意:
两种操作,一是对某个区间上的数每个数和加上他的lowbit,二是查询区间和
分析:
首先,很容易想到这题估计得用树状数组,线段树之类的数据结构解决,但问题是修改有点麻烦,对一个区间上加的数不统一,难以快速维护。解决问题需要对lowbit比较熟悉,很容易发现,在加lowbit的情况下,所有数在加了一定次之后都会变成10000….000(2)这样的数,那之后在加lowbit实际是对它乘2了。所以,这道题,如果一个区间上的数都是100000(2)形式的就乘2,并且lazy标记即可,否则暴力维护。
实际上写起来蛮麻烦的,有些地方对线段树理解不深,一直写不对一开始
AC代码:
1 |
|
1.对于节点p,如果其父节点lazy标记为0,但是p节点lazy标记不是0,那tree[p].sum的值仍然是区间(tree[p].l, tree[p].r)的和,p节点的lazy是它的子节点的待办。
2.一些奇怪地方需要return.
(1)建树时,建到叶子节点需要return,因为不需要继续往下build了,叶子节点也没办法pushup(pushup(p)是将p的儿子信息更新给父亲,叶子节点无父亲)
(2)修改时,对这个区间进行了整体的修改后也不需要继续向下,需要return
(3)查询时,查到一整个区间,直接return, 没有的话分别查两个儿子的结束后,也要return(感觉相当于pushup)
二.2021杭电多校8/HDU7059 Counting Stars
题意:
三种操作,区间减lowbit,区间每个数加上2^k(满足 2^k ≤ai <2^(k+1)),区间和查询
分析:
同上一个题,减lowbit会让他变成0,加的那个数相当于把一个数化成2进制,取它的最高位,其他位都置0,那么对于每一个数,我们这样存储它:
例如,数字1001010011100,我们用两部分存,第一部分叫high,存1000000000000,另一部分叫low,存1010011100
除此之外还需要bool变量标记区间是否全置0,以及lazy标记。
之后就是线段树常规的操作
AC代码:
1 |
|
对于像这种数值快速递降至稳定的函数(再例如区间开根号,区间求欧拉函数之类的),我们可以考虑进行暴力修改。之后再进行区间整体修改