目录
一、基本概念
现在有一个长度为 n 的数组a[],有两种操作:
1、求区间 [1,x] 的和
2、更改 a[x] 的值
这个问题怎么解决?
暴力:对于求和:直接遍历求和,对于更改:直接修改 a[x] 的值。这样的时间复杂度是多少?每次求和:O(n);每次修改:O(1),如果有大量的求和,每次O(n)的求和 时间复杂度就很高。那我们应该如何去优化呢?这就要提到树状数组了。
我们用一个树形的结构来表示数组,用叶子节点存值,可以得到下面这张图:
用 c[] 来表示子树的叶子结点的权值之和,c[] 既可以表示出单个点的值a[i],又可以表示和,而且可以用数组写出。
那么c数组的下标该如何表示呢?
转换成二进制,我们可以发现规律:
我们不难看出,与二进制数末尾的0个数有关。如果我们设末尾有k个0,可以得出以下公式:
末尾有多少个0,其实就是从低位开始的首个1在哪。我们可以通过下面这个函数得出从低位开始首个1的位置,即2^k的值:
int lowbit(int t){
return t&(-t);
}
原理也不难,在此不多加赘述,感兴趣者可自行查阅。
二、单点修改
单点修改并不难,一张示意图基本就能搞明白:
即:修改x位置的值,需要一并修改的有x+lowbit(x),一直这样修改直到修改到最后一个。
代码:
void update(int x,int y){ //x为更新的位置,y为变化的量
for(int i=x;i<=n;i+=lowbit(i)){
c[i]+=y;
}
}
三、区间查询
我们可以利用c数组来求a数组前n项和,如n=7时:
sum[7]=a[1]+a[2]+a[3]+a[4]+a[5]+a[6]+a[7]
=(a[1]+a[2]+a[3]+a[4])+(a[5]+a[6])+a[7]
=c[4]+c[6]+c[7]
写成二进制:sum[(111)]=c[(100)]+c[(110)]+c[(111)];
有什么规律?
那么我们就可以得到区间查询的代码:
int getsum(int x){
int ans=0;
for(int i=x;i;i-=lowbit(i)){
ans+=c[i];
}
return ans;
}
这就是树状数组的基本内容,它的练习题我就不放了,大家可以自行上网寻找,不懂的地方可以看题解。谢谢观看!