问题导入
给定一个长度为n的数组,完成两种操作:
1.修改一个元素(将此数加上k):单点修改
2.输出区间[l,r]的和 :区间查询
如果暴力的时间复杂度为O(n^2),我们接受不了!
这就要用到树状数组了:
树状数组
1=(0001) c[1]=a[1]
2=(0010) c[2]=a[1]+a[2]
3=(0011) c[3]=a[3]
4=(0100) c[4]=a[1]+a[2]+a[3]+a[4]
5=(0101) c[5]=a[5]
6=(0110 ) c[6]=a[5]+a[6]
7=(0111) c[7]=a[7]
8=(1000) c[8]=a[1]+a[2]+a[3]+a[4]a[5]+a[6]+a[7]+a[8]
在这之中我们可以看出,c[i]中所加的a数组的数的个数和i的二进制末尾零的个数有关系,
k为i的二进制中从低位到高位连续0的长度,c[i]就加2k个数(从i开始加,往前加2k个数)
c[i]=a[i-2K+1]+a[i-2k+2]+……+a[i]
i=8(1000)时,从8往前加,加8个数
8=(1000) c[8]=a[1]+a[2]+a[3]+a[4]a[5]+a[6]+a[7]+a[8]
末尾连续的零
求末尾有多少个零,我们就是求最后一个1在哪
int lowbit(int t){
return t&(-t);
}
这样我们就把末尾有多少零求出来了
c[i]=a[i-lowbit(i)+1]+a[i-lowbit(i)+2]+……+a[i]
单点修改
从图中我们发现如果修改a[3]的值,那么c[3],c[4],c[8]都会改变,那这之间的规律是什么呢
我们发现:
3(0011)+lowbit(3)=4(0100)
4(0100)+lowbit(4)=8(1000)
一直修改到n就停止
区间查询
从图中我们发现如果查a[1]~a[7],那么就需要c[7],c[6],c[4],这之间的规律是什么呢
我们发现:
7(0111)-lowbit(7)=6(0110)
6(0110)-lowbit(6)=4(0100)
4(0100)-lowbit(4)=0
用sum数组累加即可
一直到加到i=0就停止
单点修改和区间查询的时间复杂度都为O(logn)
代码总结
void updata (int x,int y){//单点修改
for(int i=x;i<=n;i+=lowbit(i)){
c[i]+=y;
}
}
int downdata(int n){
int sum=0;
for(int i=n;i!=0;i-=lowbit(i)){
sum+=c[i];
}
return sum;
}
完整代码(问题导入的问题)