当时学数据结构的时候,暑假训练的课上得云里雾里,自己还忙于补之前的题,导致只听完了课,内容都没有消化. 学完了树状数组,只知道怎么用,该用的时候还想不起来用...现在细想一下, 简单的树状数组就是 点修改、区间查询,能降一位O(n)到log(n).
现在有一个序列,a[n],假设n=16. 设
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[8]
........
1.lowbit()
通过观察,我们发现每一个c[i]的节点表示的和中最后一项都是c[i],他们所包含的项的个数是i的二进制最后一位1所代表的数(或者是说2^k,k为末尾零的个数),这也是lowbit() 函数要做的事情
int lowbit(int i){ return i&-i; }
其实想要求这个lowbit的话,我们最先想到的是,对于一个数x的二进制,如果我们减去1,那么从末尾开始,所有末尾的0都会变成1,最后一个1也会变成0,只有这部分会变化;所以我们用 x去异或(x-1),就会把我们用得到的位保留下来并置为1,之前没有关系的位都置为0,这一串1再和x进行与运算,就把原来是0的位去掉了,原来是1的位还是1,就得到了我们要求的lowbit的值。
int lowbit(int i){ return i&(i^(i-1)); }
i&-i是利用了负数补码的性质,取反加1,进位只影响到最后一位1,非常巧妙,以前一直在照写,却没有明白为什么,实在是惭愧。
那么,所有的c[i]节点构成了一棵树,这个数就是树状数组。
2.get()
int get(int i) {
int ans=0;
while (i>0) {
ans+=c[i];
i=i-lb(i);
}
return ans;
}
查询操作是查询区间和,将节点加到一块。比如我查询的是sum(5),5的二进制是101,从最后一个1开始取节点c[5];减去lowbit变成100,取c[4];减去lowbit变成0,停止;加和即为a[1]+...+a[5],即sum(5).
为什么是O(log(n))呢,因为查询时是二进制数中每有一个1就进行一次操作,做多有log(n)个1。
3.set()
void set(int i, int x) {
while (i<=N) {
s[i]+=x;
i=i+lb(i);
}
}
修改包含当前要修改的位置的节点即可。i每次加lowbit是将包含这个节点的值都进行一次修改,包含一个a[i]节点的c[i]节点最多有log(n)+1个。拿16和1举例,显而易见,1,2,4,8,16都包含1。复杂度还是O(log(n))级别
树状数组还能转变为区间修改点查询。
4.个人理解
觉得树状数组之所以能降低到O(log(n))的复杂度是因为,树状数组用类似二分的办法将数组储存下来。16分为左8整体,左8又被拆出来左4,以此类推...... 还有就是,利用二进制,或者说是2的次方的性质。 log也是以2为底的~ 降复杂度有时候就跟二分或者二进制(的胡搞...)有关。稍微高级一点的应用过几天在学2333