树状数组入门

树状数组可以完成一些区间加、区间求和问题,虽然适用范围不如线段树广,但常数和代码量均小于线段树。

先来看一个简单的问题:给一个长度为 \(n\) 的序列,需要做 \(q\) 次操作,操作分为两种:

  1. 将下标为 \(p\) 的元素的值修改为 \(k\)
  2. 询问区间 \([l,r]\) 内所有元素的和

只用一个数组可以做到 \(O(1)\) 修改 \(O(n)\) 求和,预处理一个前缀和数组可以做到 \(O(1)\) 求和 \(O(n)\) 修改,而树状数组的两种操作都是 \(O(\log n)\) 的。

树状数组的原理很简单:任意一个正整数都能表示成 \(2\) 的若干个不同幂次和,也就是这个数的二进制形式。定义一个数的 \(\text{lowbit}\) 为该数二进制表示中最低位的 \(1\),如 \(\text{lowbit}(6)=2\)。树状数组中下标为 \(p\) 的元素维护原数组中 \((p-\text{lowbit}(p),p]\) 的区间和。那么如果要求 \([1,p]\) 的区间和(即前缀和),只需不断统计 \(p\) 对应的区间和并将 \(p\) 减去其 \(\text{lowbit}\) 即可。这其实相当于将一个前缀分成了 \(\log\) 段,从后往前把它们加了起来。

修改操作正好相反,将 \(p\) 不断加上 \(\text{lowbit}\),只有这些位置对应的区间包含原来的 \(p\)

树状数组的原理也可以这样解释:如果在线段树上查询前缀和,右儿子的值是没有用的,所以把线段树的所有右儿子都去掉,剩下的结构就是树状数组了。

那么如何实现 \(\text{lowbit}\)?利用有符号数的储存方式,可以这样写:int lowbit(int x){return x & -x;}(补码的性质)。

由于树状数组和前缀和有很多相似之处,我们很容易将其扩展到二维,实现对矩阵的操作。

posted @ 2021-09-23 23:37  Theophania  阅读(46)  评论(0)    收藏  举报