树状数组之前缀和
原理
- 下标i 管辖的区域大小:i 对应的二进制保留最低位的1, 其它位全部置0。
- c[i] 的父亲的下标:用lowbit(i) 表示i 的管辖区域,那么i+lowbit(i) 就表示i 父亲结点的下标
- c[i] 左边的仅挨着自己管辖区间的点的下标:i−lowbit(i)
问题 1: 我们如何去求 lowbit(i) :i & (-i) 就会把 i 的二进制除最低位 1 外全部置 0。例如:
- 所有的数字在内存中都是补码存储的
- 数字 4 的补码是00000100数字
- -4 的补码是11111100
4&(−4)=00000100&(11111100b)=100b=4
这个运算我们就叫做 lowbit(i), 正好可以得到下标 i 的管辖区域
问题 2: 如何求一段区域的和 (前缀和):
- 如果我们S[12]=A[1]+A[2]+…+A[12]
- 我们知道:
C[12]=A[12]+A[11]+A[10]+A[9]
因为:lowbit(12)=4, 表示C[12] 管辖大小:4 - 12−lowbit(12)=12−4=8
+lowbit(8)=8 所以 8−lowbit(8)=0
所以: A[1]+A[2]+…+A[12]=C[12]+C[8]
核心操作
const int N = 100060 //数组大小
int n //动态变量输入的数组大小
int c[N],ans[N] //数组c是树状数组,ans是最后主函数输出的答案
int lowbit(int x) { return (x & -x); } //求c数组下标
int fa(int p) { return (p+lowbit(p);} //求p的父节点
int left(int p) { return (p-lowbit(p);} //求p的上一个管辖范围的最左节点的下标
int getsum(int n){ // 求区间0~x 的前缀和
int sum = 0;
while( n>0 ){
sum += c[n];
n = lowbit(n);
}
return sum;
}
void change(int x, int num){ //更新节点x为数据num
while( x>=N ){ //节点x更新并且带动x之后的所有节点更新
c[x]+=num;
x += lowbit(x);
}
}
例题
P3374 【模板】树状数组 1
思路: 最基本的查询前缀和的问题
#include<iostream>
#include<string.h>
#include<stdio.h>
using namespace std;
#define Ios ios::sync_with_stdio(false) , cin.tie(0) , cout.tie(0);
#define mem(a,b) memset(a,b,sizeof(a))
const int N = 600050;
int c[N],ans[N];
int lowbit(int x){
return (x & -x);
}
int getsum(int n){
int res = 0;
while(n>0){
res+=c[n];
n-=lowbit(n);
}
return res;
}
void change(int x,int num){
while(x<=N){
c[x]+=num;
x+=lowbit(x);
}
}
int main(){
int n,m;
//初始化数组
mem(c,0);
mem(ans,0);
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++){ //此处由于我们省去了取定义原始数组a,所以我们利用change函数初始化c数组,有点贪心的思想
int t;
scanf("%d",&t);
change(i,t);
}
for(int i=1;i<=m;i++){
int flag,x,y;
scanf("%d%d%d",&flag,&x,&y);
if(flag == 1){
change(x,y);
}else if(flag == 2){
printf("%d\n",getsum(y)-getsum(x-1)); //区间和等于右端点的前缀和减去左端点的前缀和
}
}
return 0;
}