模板题:
有一个int型数组arr[],并且多次修改数组中的数字,求数组某个区间中所有数字的和。
模板代码:
int arr[n];//原数组
int c[n]; //树状数组
int lowbit(int i){
return i & -i;
}
void update(int x,int y){//更新x位置的值,与原值差值为y
while(x<=n){
c[x]+=y;
x+=lowbit(x);
}
}
int sum(int i){//查询区间[1,i]
int ret=0;
while(i>0){
ret+=c[i];
i-=lowbit(i);
}
return ret;
}
让我们依次从上往下看来理解这份代码:
1.int arr[n]
这是我们要进行的操作的数组,由用户给出,这个就不需要多讲了。
2.int c[n]
这是我们根据原数组构造出来的树状数组,它的赋值由后面的更新函数updata()实现。
3.int lowbit(int i)
树状数组实现中最重要的一环,这函数实现了找到数字i在二进制下最末位1对应的数值。举个例子:
但这个函数的代码只有一行,它是怎么实现的呢?
首先,我们要对整数的二进制有一定的了解:
1)整数i的反码为-i(连符号位一起变);
2)求整数i的反码:写出二进制下的i,从右往左找到第一个1,这个1和其右边的0都不要动,左边的数取反,得到的数就是i的反码。
基于上面的原理,可得到下面的表格:
i | -i | i&-i |
---|---|---|
100010B | 011110B | 10B |
111000B | 001000B | 1000B |
看,经过上面的推理,我们就得到了一行代码实现了找到数字i在二进制下最末位1对应的数值的结果。
OK,知道lowbit()这个函数的作用和实现了,我们看下一个函数。
4.void update(int x,int y)
这个函数用于修改更新树状数组c[]中的值。
我们先来看看我们要实现的树状数组c[]的结构:
在这张图中,绿色方块代表C[i]的值,和它连接的紫色部分代表它下包的arr[]数组的数值,即有:
c[1]=arr[1]
c[2]=arr[1]+arr[2]
c[3]=arr[3]
c[4]=arr[1]+arr[2]+arr[3]+arr[4]
c[5]=arr[5]
c[6]=arr[5]+arr[6]
c[7]=arr[7]
c[8]=arr[8]
......
了解了c[]数组的结构,我们再返回来看函数update()是怎么实现这个结构的。函数的参数x代表更新数字的位置,y代表更新数字与原来数字的差值。
根据c[]数组的结构,当我们更新了c[3]的值,那c[4],c[8],c[16]…的值也会更新,所以问题就变成了寻找 3,4,8,16… 这个序列的规律。然后我们就可以发现(其实真的很难发现,还是得看代码才知道):
x=3, c[3]+=y
3+lowbit(3)=4 , c[4]+=y
4+lowbit(4)=8, c[8]+=y
8+lowbit(8)=16, c[16]+=y
......
上述式子等价于:
int x=3,y=差值;
while(x<=n){
c[x]+=y;
x+=lowbit(x);
}
}
通过这种向上更新的方法,我们就能以 O(logn) 的复杂度更新数组区间和了。
5.int sum(int i)
继续看那张c[]数组结构图,如果我们想查询区间[1,8],我们只需要得到c[8]就行,但如果想查询区间[1,9],我们就需要得到c[8]+c[9]了,而[1,11]则更复杂,我们要得到c[8]+c[10]+c[11]。与更新函数相似,查找[1,11]变成了寻找序列 11,10,8… 的规律。然后我们就可以发现(其实真的很难发现,还是得看代码才知道):
x=11 ret+=c[11]
11-lowbit(11)=10 ret+=c[10]
10-lowbit(10)=8 ret+=c[8]
8-lowbit(8)=0 跳出
上述式子等价于:
int x=11,ret=0;
while(x>0){
ret+=c[x];
x-=lowbit(x);
}
这样我们就找到了区间[1,x]的和,当我们要查询区间[left,right]的时候,我们只要sum(right)-sum(left-1)就行。