树状数组是一个十分高效的数据结构,当要频繁的对数组元素进行修改,同时又要频繁的查询数组内任一区间元素之和的时候,可以考虑使用树状数组。
一般来说,树状数组能做到的线段树都能做到,但线段树能做到的树状数组不能做到,但树状数组相对于线段树代码复杂程度低(总代码也就线段树一个函数那么长),速度更加快。
树状数组的核心是一个叫做lowbit的函数,这个函数的功能是把一个整数的二进制中最低的1这一位取出来,根据补码原理我们只要这样做就行了。
int lowbit(int x)
{
return x&(-x);
}
具体的原理这里就不解释了,你可以自己用几个数算一下。
来观察这个图:
令这棵树的结点编号为C1,C2…Cn。令每个结点的值为这棵树的值的总和,那么容易发现:
C1 = A1
C2 = A1 + A2
C3 = A3
C4 = A1 + A2 + A3 + A4
C5 = A5
C6 = A5 + A6
C7 = A7
C8 = A1 + A2 + A3 + A4 + A5 + A6 + A7 + A8
这里有一个有趣的性质:
设节点编号为x,那么这个节点管辖的区间为2^k(其中k为x二进制末尾0的个数)个元素。因为这个区间最后一个元素必然为Ax,
所以很明显:Cn = A(n – 2^k + 1) + … + An
2^k实际上就是上面所说的lowbit()
修改
当我们需要对树进行修改时,只需将修改的这个点的所有祖先,一个点的祖先即是x+lowbit(x)
void Modify(int n, int delta)
{
while(n <= N)
{
c[n] += delta;
n += lowbit(n);
}
}
求和
当我们需要对树进行儿子时,只需求这个点的所有儿子的和,一个点的儿子即是x-lowbit(x)
int Sum(int n)
{
int result = 0;
while(n != 0)
{
result += c[n];
n -= lowbit(n);
}
return result;
}