刚学完树状数组,所以来写篇博客理下思路。
树状数组长这样:
跟线段树不同,这个是每一竖列的c[i]代表每一个子树的权值(子树与他相连的左下方的部分),与线段数的长代码不同,树状数组主要靠二进制之间的某种奇妙的关系来进行
根节点的子节点如何确定呢?
看下面这些式子:
C[1]=(01)=A[1];
C[2]=(10)=A[1]+A[2];
C[3]=(11)=A[3];
C[4]=(100)=A[1]+A[2]+A[3]+A[4];
C[5]=(101)=A[5];
C[6]=(110)=A[5]+A[6];
C[7]=(1001)=A[7];
C[8]=(1000)=A[1]+A[2]+A[3]+A[4]+A[5]+A[6]+A[7]+A[8];
仔细观察,我们会发现他需要A[i]数字的个数和二进制末尾0的个数有关,0个数为0,需要1个,0个数为1,需要2个,0个数为3,需要8个
所以C[i]二进制末尾0的个数为k,则C[i]=A[i]+A[i-1]+...+A[i-2^k+1];
代码可以用
int lowbit(int x)
{
return x&(-x);
}
返回的值是根据x求出的2^k(二进制末尾0的个数);
已知2^k,C[i]全部求出之后,就可以不用再用A[i]了,之后就用C[i],进行树间的更新查询操作
更新:
更新A[3]时,我们需要更新C[3]C[4]C[8]
转化成二进制:
3:0011
4:0100
8:1000
3+1=4
4+4=8
1=2^0
4=2^2
所以这个还要和我们上面讲的二进制末尾0的个数相关联(我也不知道为什么)
所以3+lowbit(3)=4
4+lowbit(4)=8
我们又要用到上面那个代码了.
知道这个之后,我么就可以进行更新操作了:
void add(int pos,int y)
{
for(int i=pos; i<=n; i+=lowbit(i)) //确定下一个i
val[i]+=y; //这里是增加减少,如果是直接改值,直接写等号即可
}
查询
与更改操作相反
查询7这个节点之前所有值的和sum[7]时
sum[7]=C[7]+C[6]+C[4]
还是根据二进制
7:0111
6:0110
4:0010
7-1=6,6-2=4
7-2^0=6,6-2^1=4
观察发现,7末尾0个数为0,6末尾0个数为1;
代码:
int getsum(int x)
{
int ans=0;
for(int i=x; i; i-=lowbit(i)) //确定下一个根节点
ans+=val[i];
return ans;
}
不管数据是不是8,都按上述操作,不影响.
结束~