第一次尝试写甲级三十分的题目,据说要用到树状数组,去学习了一下
在解题过程中,我们有时需要维护一个数组的前缀和S[i]=A[1]+A[2]+...+A[i]。
但是不难发现,如果我们修改了任意一个A[i],S[i]、S[i+1]...S[n]都会发生变化。
可以说,每次修改A[i]后,调整前缀和S[]在最坏情况下会需要O(n)的时间。
当n非常大时,程序会运行得非常缓慢。
因此,这里我们引入“树状数组”,它的修改与求和都是O(logn)的,效率非常高。
树状数组 重点是在树状的数组
大家都知道二叉树吧
叶子结点代表A数组A[1]~A[8]
现在变形一下
![](https://i-blog.csdnimg.cn/blog_migrate/4d41c3fd369873eef74cf7d0c65caf15.jpeg)
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[]数组的结点序号转化为二进制
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];
对照式子可以发现 C[i]=A[i-2^k+1]+A[i-2^k+2]+......A[i]; (k为i的二进制中从最低位到高位连续零的长度)例如i=8时,k=3;
那么怎么给定一个下标i,怎么找出这个k呢?注意到2的k次方,正好是下标 i的最低位的1以及其后的零组成的二进制数值
使用lowbit函数求出这个2的k次方的大小
int lowbit(int t)
{
return t&(-t);
}
注释1:计算机中负数用原数的补码,相当于原数按位取反之后加一,加的这个1使得原数按位取反后的数值,最后那几个连续的1,全部变成0(对应的原数的这几位也是0),位数最低的那个0,会被加为1,而较高的那几位不会被影响。这样以来,按位与得到的二进制数中,只有最低位的1所在的那个位不是零了。
int getsum(int x)
{
int ans=0;
for(int i=x;i>0;i-=lowbit(i))
ans+=tree[i];
return ans;
}
void add(intx,int y)
{
for(int i=x;i<=n;i+=lowbit(i))
tree[i]+=y;
}