前言
学习树状数组之前需要先了解什么是前缀和,以数组a[i]为例,前缀和数组s[i]表示前i个元素之和(a[1…i])。也常用于计算区间内的和,如数组中区间(i,j)的和,可利用前缀和数组在o(1)时间内算出:s[j] - s[i-1],但构建前缀和数组往往需要o(n)的时间。
而树状数组同样用于表示前缀和,但它的构建只需o(logn)时间,计算区间和时间o(1),但每次询问一个位置的前缀和以及修改一个数组元素后维护树状数组都需要o(logn)时间。
正文
数组数组以树形结构维护前缀和,将时间复杂度降至o(logn)。主要有add(x,c)和ask(x)两个操作。
树状数组以t[i]为例,维护数组a[i]的前缀和。
- t[i]存储以i为根的子树的和,覆盖范围为lowbit(i) (lowbit:二进制表示后的最后一位1)。如t[6],6用二进制表示为0110,则其覆盖范围为2,则t[6]存储a[5]+a[6]两个元素的和。
int lowbit(int x){
return x & -x;
}
- 树状数组中i节点的父节点为i+lowbit(i)。如6 : 0110。其父节点:0110+0010 = 1000,其父节点为8。
add操作
修改数组a[i]中一个元素的值,需要修改树状数组中所有覆盖它的父节点的值。如修改a[3]的值,需要修改t[3] t[4] t[8]。
void add(int x,int c){
for(int i=x;i<=n;i+=lowbit(i)) t[i]+=c;
}
ask操作
询问1到i前缀和。
如询问以7结尾的前缀和,7 : 0111。还需要找到0100、0110。
int ask(int x){
int res = 0;
for(int i=x;i;i-=lowbit(i)) res += t[i];
return res;
}
以上图片来源