对文档提及的不够清楚的,可以结合文章末尾给出的视频链接学习
1.为什么需要树状数组
问题 1.假如给你一个数组 A[1] …A[m], 现在要求对数组部分区间的求和.
问题 2. 假如给你一个数组 A[1] …A[m], 现在要求对数组部分区间的每一个元素 +k .
问题3. 假如给你一个数组 A[1] …A[m], 现在要求对数组某一区间进行求和,也要对某一区间的元素 +k.
上面三个问题,可以进行通过遍历区间的每一个元素来求解,但是如果数据大,就会极大的减低代码运行的效率
为什么需要树状数组, 数组可以提高普通数组进行 区间查询 , 区间修改的效率
2. 什么是树状数组
树状数组是为了解决数据压缩里的累积频率的计算问题的一种数据模型,现多用于高效计算数列的前缀和,区间和。
三大作用: (高效进行)
- 单点修改, 区间查询
- 单间查询,区间修改
- 区间修改,区间查询
3. 学习
3.1 前置知识
(学习树状数组前,需要了解)
Lowbit 函数 :
定义一个Lowbit函数,返回参数转为二进制后,最后一个1的位置所代表的数值.
例如:
- Lowbit(10) (10的二进制是 1010)的返回值将是2;
- Lowbit(12) (12的二进制是 1100)返回4;
- Lowbit(8) (1000)返回8。
程序上,(x&(-x))表明了最后一位1的值,
public static int lowbit(x){
return x&(-x)
}
所以看到 x&(-x)
就应该想到lowbit,也就是x二进制最后一个1所代表的数.
3.2 树状数组(长什么样)
现在一个数组a[8] ,转换为树状数组t[8].
- t[1] = a[1];
- t[2] = a[1] + a[2];
- t[3] = a[3];
- t[4] = a[1] + a[2] + a[3] + a[4];
…
通过观察结论 t[x]的父节点 t[x + lowbit(x)] ; (前人总结的经验,没必要纠结我怎么就没想到呢)
t[1]的父节点 t [1 + lowbit(1)] = t[2]
t[4]的父节点 t [4 + lowbit(4)] = t[8]
3.3单点修改, 区间查询
单点修改: 因为树状数组单位元素的值代表的部分前缀和.
例如 如果要在a[3] + 3; 那么就要在 t[3] , t[4] ,t[8] 分别加上 3
public static void add(i, k){
for( ; i<= t.length(); t += i&(-t) ){ // 下一个元素加上父节点
t[x] +=k;
}
}
前i项和 : 例如 t[5]的前 5项和 为 t[4] + t[5] , 要不断求出前项进行累加
public static int ask(int x){
int ans = 0;
for( ; x>0; x-= x&(-x)){
ans += t[x];
}
return ans;
}
**区间查询 ** : 求 l … r 项的和 ,为 前 r 项和 - 前 l-1 项的和.
int n = ask(l) - ask(r-1);
3.4单间查询,区间修改
3.4.1 差分数组
学习1
学习2
因为其他博主已经写得很好了,我就不复述了,可以自行参考文章阅读.
3.4.2
利用差分数组的思想,树状数组t[n],保存也是差分数组 .
t [i] = (a[i] - a[i - 1 ]).
区间修改 :
public static void add(i, k){
for( ; i<= t.length(); t += i&(-t) ){ // 下一个元素加上父节点
t[x] +=k;
}
}
public static void change(int l, int r , int k){
add(l,k);
add(r+1,-k);
}
单点查询 : a[x] + ask[i] ; 相当 x + x的增量
public static int ask(int x){
int ans = 0;
for( ; x>0; x-= x&(-x)){
ans += t[x];
}
return ans;
}
public static int change(int i){
return a[i] + ask[i];
}
3.5区间修改,区间查询 (暂时跳过)
还没想好怎么写比较好 , (手动狗头保命 )
Ps: 参考资料
视频链接 :bilibili
博客链接: