树状数组是一种代码量小,维护区间的数据结构
他可以实现:
1.区间修改,单点查询
2.单点修改,区间查询
当然,二者不可兼得,大人全都要的话,请选择线段树
前置知识:
lowbit(x)操作:
获取x二进制的最后一位1以及其后面的0所组成的数
比如x等于6时,其二进制为110,那么lowbit(6)就等于2,其二进制为10
这里有:
lowbit(x)=x&(-x)
树状数组的特性:
对于树状数组tr[N]而言
tr[x+lowbit(x)]是tr[x]的父亲
对于区间[1,x]而言
其区间和等于
操作:
设原数组为a[N],大小为n,树状数组为tr[N],
1.单点修改,区间查询:
假设我们要对a[x]的权值进行修改,那么我们对tr[x]进行修改,然后不断pushup他的父结点,也就是tr[x+lowbit(x)]就可以了,直到pushup到tr[n]
如果我们要对区间[l,r]进行查询,我们只要求出区间[1,l-1],[1,r],然后利用前缀和就可以求出区间[l,r]的和了
实现代码如下:
int lowbit(int x){
return x & (-x);
}
//单点修改
void pointAdd(int x,int k){
for (int i = x; i <= n;i+=lowbit(i)){
tr[i] += k;
}
}
//查询区间[l,r]
int queryLine(int l,int r){
int sum = 0;
for (int i = r; i;i-=lowbit(i)){
sum += tr[i];
}
for (int i = l - 1; i;i-=lowbit(i)){
sum -= tr[i];
}
return sum;
}
2.区间修改,单点查询
其实本质上还是利用树状数组单点修改的方便性
这里我们构造原数组的差分数组d,然后用树状数组来维护数组d
当我们需要对原数组的区间[l,r]进行加权值k的修改时,只需要对差分数组d[l]和d[r+1]进行单点修改就可以了
当我们需要对原数组的a[x]进行查询时,只要求出d数组[1,x]的区间和就可以了
实现代码如下:
//初始化差分数组以及树状数组
void init(){
for (int i = 1; i <= n;i++){
d[i] = a[i] - a[i - 1];
for (int j = i; j <= n;j+=lowbit(j)){
tr[j] += d[i];
}
}
}
//区间修改
void change(int l,int r,int k){
for (int i = r + 1; i<=n;i+=lowbit(i)){
tr[i] -= k;
}
for (int i = l;i<=n;i+=lowbit(i)){
tr[i] += k;
}
}
//单点查询
int query(int x){
int sum = 0;
for (int i = x; i;i-=lowbit(i)){
sum += tr[i];
}
return sum;
}