树状数组学习笔记
树状数组
就是把线段树每个节点的右儿子去掉,然后所有节点连向可追溯的第一位祖先
线段树的结构(手绘图片累死了QwQ):

去掉右儿子,链向祖先,同时每个节点都标号为自己覆盖的最后一个位置,就将线段树改造成了树状数组:

一些性质
- 树状数组是右儿子信息缺失的线段树
- 每次前缀查询到的子树的大小之和刚好为前缀长度
- 树状数组中节点的编号是节点的右端点位置
- 树状数组中节点的长度是节点编号的\(\textbf{lowbit}\)
可实现操作
前缀查询
由于右儿子的缺失,线段树区间查询的能力已经完全丧失了,但是可以支持前缀查询,通过倍增的方式将一个前缀查询划分成\(\log\)段长度不同且长度为\(2^k\)的区间,然后合并得到答案。
这个过程可以轻松的实现,不过这里要讲另一种更常用的方法:确定最后一个位置然后倒推回去。
令\(c_i\)表示树状数组中的节点\(i\),\(f_i\)表示区间\([1,i]\)的信息,那么:
\[f_i=c_i+f_{i-\operatorname{lowbit}(i)}
\]
其中\(\operatorname{lowbit}(i)\)表示\(i\)的最后一个二进制位,可以用\(x\textbf{&}-x\)来实现.
这个操作看似玄学,实际上有道理的很。
我们考虑到两件事:
- 在树状数组中,节点\(i\)的最后一个位置就是\(i\)位置。
- 在树状数组中,节点\(i\)的长度正是\(\operatorname{lowbit}(i)\).
一个点的右端点减去长度,不就是它的前驱位置吗?
至此我们搞定了前缀查询,至于区间查询,只有满足可差分性的情况能通过前缀作差的方式来实现。
树状数组上二分
不需要满足可查分,过程和线段树二分类似,大致的思路是缩小范围。
首先锁定合法区间\([1,2^k]\),然后向下搜索:

现在正在红圈位置,锁定区间\([5,8]\)。
不难发现区间\([l,r]\)的左儿子编号为\(\frac{l+r}{2}\).
检查左儿子,如果合法进入左儿子,不然就进入右儿子。
搜到叶子结束。
特殊情况
如果是针对一段前缀二分的话,可以先倍增拆成\(\log\)段区间,然后锁定其中某一段后再二分。
区间修改,单点查询
利用差分思想,令\(\operatorname{sum}_i=a_i\),每次修改时,在区间左端点处加,右端点处减。
区间修改,区间查询
也是差分,构造\(\sum_{i=1}^{n}r_i=a_n\),则
\[\sum_{i=1}^{n}a_i=\sum_{i=1}^{n}\sum_{j=1}^{i}r_i=\sum_{i=1}^{n}r_i\times n-\sum_{i=1}^{n}r_i\times(i-1)
\]
所以我们分别维护前缀\(r_i\)和\(r_i\times(i-1)\)即可。

浙公网安备 33010602011771号