树状数组(Binary indexed tree)
A Fenwick tree or binary indexed tree is a data structure that can efficiently update elements and calculate prefix sums in a table of numbers.
来自Wikipedia
树状数组是一种求解前缀和问题的简单数据结构,能够在O(logn)的时间复杂度内解决区间求和问题,主要支持两种操作
- 单点修改
- 区间操作
主要思路
类比任何数均可以拆分为有限个不同2的幂次的求和,原论文作者认为一串序列的求和同样可以拆分为有限个不同长度为2的幂次的序列的和。在数字的拆分中,其二进制表示中1的个数即为不同2的幂次,例如 “10”,“12”
自然想到一个问题:如何快速的找到任意数的不同二次幂的组合?引出了lowbit思路:
通过原码与补码求逻辑与运算,求得目标数字的最右边的一个1
原数字减去lowbit,获得少了一位1的数字
重复操作,直到 $x_i = 0$
重复此过程每一步计算得到的 $lowbit(x_i)$ 即为原数字的不同2的幂次拆分,以14为例:
如何将对应拆分思路迁移到前缀和?
不妨用 $C[i]$ 表示 目标数组$f[1]…f[i]$的前缀和,通过$lowerbit$操作,我们可以快速找到任意区间长度 对应的二次幂区间长度的组合。
问题是我们如何设计存储方式,提前存储这些二次幂区间长度的区间和?观察14转化为组合的过程,对应到区间表达形式为:
可以得到每个区间右边界可以由上个区间右边界减去lobit得到,而区间的长度等于当前区间右边界的lowbit(拆分区间的递推关系),自然可以想到,将区间$(i - lowbit(i),i]$的和存储到 index = i的位置
- 因此定义$tree$数组,对于任意$tree[i]$,其存储范围为 $(i-lowbit(i),i]$区间的和,如下:
任意前缀和 $C[i]$计算,按照上述拆分过程,可以转化为不断的减去最右边一的过程
1 | for(int index = i;index > 0;index -= lowbit(index)): |
如何支持单点修改
若修改 $f[i]$,需要修改所有包含$f[i]$ 的 $tree[index]$, 我们要找到所有包含当前元素的区间,假设 $tree[idx]$ 包含 $f[i]$,则 $i$一定满足:
来自博客Binary indexed tree-树状数组 的形象解释
代码实现
lowbit()函数
1 | public int lowbit(int x){ |
求前缀和
1 | //预先处理好的子区间和数组 |
更新方法
1 | //预先处理好的子区间和数组 |
初始化树状数组
wekipedia上的一个从数组[1,2,3,4,5]构建树状数组的过程:
1 | int tree[maxIndex] |
模板题目
- luogu P3374 :树状数组1
- 使用scanner做输入会出现三个测试用例TLE(坑爹)
- luogu P3368 :树状数组2
- 通过差分的思路,将树状数组支持的操作变为
- 单点修改->区间修改
- 区间查询->单点查询
- 通过差分的思路,将树状数组支持的操作变为
- luogu P2880 :牛飞盘比赛
- 树状数组维护值从 区间和 变为 区间内最值
- update操作变为最大值更新,查询操作递归完成