Loading

树状数组学习笔记

树状数组

就是把线段树每个节点的右儿子去掉,然后所有节点连向可追溯的第一位祖先

线段树的结构(手绘图片累死了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)\)即可。

二维树状数组

posted @ 2021-03-13 14:54  SmilingKnight  阅读(81)  评论(1)    收藏  举报