裸的 树状数组
树状数组
C[i]代表子树的叶子节点的权值之和:
C[1]=A[1];
C[2]=A[1]+A[2];
C[3]=A[3];
C[4]=A[1]+A[2]+A[3]+A[4];
C[5]=A[5];
C[6]=A[5]+A[6];
C[7]=A[7];
C[8]=A[1]+A[2]+A[3]+A[4]+A[5]+A[6]+A[7]+A[8];
将C数组的下标i转化成二进制:
1=(001) C[1]=A[1];
2=(010) C[2]=A[1]+A[2];
3=(011) C[3]=A[3];
4=(100) C[4]=A[1]+A[2]+A[3]+A[4];
5=(101) C[5]=A[5];
6=(110) C[6]=A[5]+A[6];
7=(111) C[7]=A[7];
8=(1000) C[8]=A[1]+A[2]+A[3]+A[4]+A[5]+A[6]+A[7]+A[8];
项数 等于 二进制最右边(最低位)的1表示的整数
lowbit
int lowbit(x)
{ return x & -x; }
补码的另一种求法是 找到最右边的1 把这个数字左边的全部数字取反
前缀和
int sum(int i) //求区间[1,i]所有元素的和
{ int ret=0;
while(i>0){
ret+=C[i]; //从右往左区间求和
i-=lowbit(i);
}
return ret;
}
已经解决的问题:
在构造出树状数组C[i]的前提下,求前缀和
如何进行区间查询?
sum® - sum(L-1)
下一个问题:单点更新
单点更新 更新后缀和
void update(int i,int val)//单点更新(影响多个C元素)
{ while(i<=n){
C[i]+=val;
i+=lowbit(i); //由叶子节点向上更新C数组
}
}
//更新(从小到大)是查询(从大到小)的逆过程
代码
class NumArray {
int[] c;
int[] a;
int len;
public NumArray(int[] nums) {
a = new int[nums.length+1];
System.arraycopy(nums, 0, a, 1, nums.length);
len = nums.length+1;
c = new int[len];
for(int i = 1; i<len; i++){
initialize(i, a[i]);
}
}
private void initialize(int i, int val){
while(i<len){
c[i] += val;
i += lowbit(i);
}
}
public void update(int i, int val) {
i++;
int delta = val - a[i];
a[i] = val;
while(i<len){
c[i] += delta;
i += lowbit(i);
}
}
private int lowbit(int x) {
return x&(-x);
}
public int sumRange(int i, int j) {
int sumi = getSum(i);
int sumj = getSum(j+1);
return sumj - sumi;
}
private int getSum(int i) {
int res = 0;
while(i > 0){
res += c[i];
i -= lowbit(i);
}
return res;
}
}