树状数组基本原理
它可以解决的问题
1.快速求前缀和 O(logn)
2.修改某一个数 O(logn )
使用普通数组(前缀和数组)的话
求前缀和的时间复杂度是O(n),修改某个数时间复杂度是O(1)
树状数组其实就是想办法,将两个极端的复杂度折中一下
思路:基于二进制数据
将长度为n的数组分成log(n)个的不同长度的区间
可以看到,对于每个区间来说,
比如对于第一个区间(x - 2^i1,x],其实是x二进制表示的最后一位1
推广开就是
对于(L, R]区间来说,区间长度一定是R的二进制表示的最后一个1的所对应的次数
所以区间就可以分成
c[R] = a[R - lowbit(R)+1, R] 表示的意义是区间[R - lowbit(R)+1, R]数组内的和
c[x]是以x结尾的长度为2^k的区间和
各个区间的长度是 lowbit(x),使用图形说明如下(a数组和c数组的对应关系):
a和c的关系如上图
求每一个c[x]的时候
c[n] = an + c[n - 1] + c[n-1-lowbit[x - 1]] + .... + c 一直到c为0为止
如何通过子节点找父节点 ==》修改数组
通过上面对c数组的分析,我们知道父节点对应的数组的长度 被分解为了多个长度的数组
假设子节点为x, 父节点是p
那么
如果得到某一个子节点,如何找父节点
x:…011…11000…000
p:…100…00000…000
可以通过
p = x + lowbit(x);
直接让最后一位进位了,非常nb
修改操作的流程也全部出来
通过图片可以知道,更改了数组一个数,那个数的tr数组路径上的数值都会改变
更改a[5]的值,需要改变的前缀和c数组的有小标5,6, 8,16四个数
通用模板
int a[N]; //原数组
int tr[N]; //树状数组
int Greater[N], lower[N];//以i结尾的,比a[i]大的 和 以i结尾的,比a[i]大的数的个数
int lowbit(int x)
{
return x & -x;
}
void add(int x, int c)
{
for (int i = x; i <= n; i += lowbit(i)) tr[i] += c;
}
int sum(int x)
{
int res = 0;
for (int i = x; i; i -= lowbit(i)) res += tr[i];
return res;
}