树状数组
优点:代码短,运行效率高(大部分情况下与线段树相比大约差10倍),支持修改(在线做法)
能用树状数组做的尽量别用线段树(杀鸡不用牛刀)
解决问题:动态快速求前缀和(O(logn))
- 给某个位置上的数加一个数(单点修改)
- 快速求前缀和(区间查询)
个人理解:
在某些情况下,需要实现对序列中进行区间增加(差分能做到),之后再进行查询(前缀和能做到),但是前缀和是离线的不支持修改,而差分又不能很快的求出区间和,因此引出了这种方法。
构造方法:
- 维护一个和原数组
a[n]
一样长的数组(注意这里下标是从1开始的) - 一共有四层,根据每个下标二进制表示数字的末尾有几个
0
代表第几层 - 例如其中奇数位置的值都等于原数组
c[奇数] = a[奇数]
,因为它们的末尾有0个0
,代表这都是第0层 c[x]
其实表示的是下标为(x - 2^k,x]
这个区间的和,其中k
为x
的二进制末尾0
的个数,2^k
可以用lowbit(x) = x&-x
表示
例题代码:
leetcode307. 区域和检索 - 数组可修改
class NumArray {
public:
vector<int> tr;
vector<int> num;
int lowbit(int x)
{
return x&-x;
}
void add(int val,int idx)
{
for(int i = idx;i<tr.size();i+=lowbit(i))
{
tr[i] += val;
}
}
int query(int idx)
{
int sum = 0;
for(int i = idx;i>0;i-=lowbit(i))
{
sum += tr[i];
}
return sum;
}
NumArray(vector<int>& nums):tr(nums.size()+1),num(nums) {
for(int i = 0;i<nums.size();i++)
{
add(nums[i],i+1);
}
}
void update(int index, int val) {
add(val - num[index],index+1);
num[index] = val;
}
int sumRange(int left, int right) {
return query(right + 1) - query(left);
}
};