from: http://old.blog.edu.cn/user3/Newpoo/archives/2007/1712628.shtml
树状数组
树状数组是一个可以很高效的进行区间统计的数据结构。 当 要 频繁的对数组元素进行修改,同时又要频繁的查询数组内任一区间元素之和的时候,可以考虑使用树状数组.
树状数组是一个查询和修改复杂度都为log(n)的数据结构,假设数组a[1..n], 那么查询a[1]+...+a[n]的时间是log级别的,而且是一个在线的数据结构,支持随时修改某个元素的值,复杂度也为log级别。
来观察这个图:
令这棵树的结点编号为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
...
C16 = A1 + A2 + A3 + A4 + A5 + A6 + A7 + A8 + A9 + A10 + A11 + A12 + A13 + A14 + A15 + A16
树状数组的最基本功能就是求比某点x小的点的个数(这里的比较是抽象的概念,可以使数的大小,坐标的大小,质量的大小等)。
比如给定个数组a[5] = {2, 5, 3, 4, 1},求b[i] = 位置i左边小于等于a[i]的数的个数.如b[5] = {0, 1, 1, 2, 0},这是最正统的树状数组的应用,直接遍历遍数组,每个位置先求出Getsum(a[i]),然后再修改树状数组Update(a[i], 1)即可。当数的范围比较大时需要进行离散化,即先排个序,再重新编号。如a[] = {10000000, 10, 2000, 20, 300},那么离散化后a[] = {5, 1, 4, 2, 3}。
但我们想个问题,如果要求b[i] = 位置i左边大于等于a[i]的数的个数呢?当然我们可以离散化时倒过来编号,但有没有更直接的方法呢?答案是有。几乎所有教程上树状数组的三个函数都是那样写的,但我们可以想想问啥修改就是x不断增加,求和就是x不断减少,我们是否可以反过来呢,答案是肯定的。
这时候,我们要改变一下树状数组的构成,仍以开始的例子说明:
C[i]=a[i]+a[i+1]+...+a[i+2^k-1]。
比如:
C1=a1
C2=a2+a3
C3=a3
C4=a4+a5+a6+a7
C5=a5
C6=a6+a7
C7=a7
C8=a8
这时候,数组C[i]中保存的是从i开始到i+2^k-1位置的元素之和。这时对a[n]进行修改后,需要相应的修改c数组中的p1 , p2 , p3 ...等一系列元素
void Update(int x, int c)
{
int i;
for (i = x; i >= 1; i -= Lowbit(i))
{
tree[i] += c;
}
}
int Getsum(int x)
{
int i;
int temp(0);
for (i = x; i < maxn; i += Lowbit(i))
{
temp += tree[i];
}
return temp;
}
我们只是将两个函数中的循环语句调换了下,现在每次要修改点x的值,就要修改(1, …x-Lowbit(x-Lowbit(x))), x-Lowbit(x), x)路径,而求和就变成求(x, x+Lowbit(x), x+Lowbit(x+Lowbit(x))),…)这条路径的点。而这不正好就是大于等于x的点的求和吗?
所以我们既可以修改x增大的路,求和x减小的路;也可以修改x减小的路,求和x增大的路,根据题目的需要来决定用哪种。
树状数组可以扩充到二维。在二维情况下:
C[x][y] = ∑ a[i][j], 其中,
x-lowbit(x) + 1 <= i <= x,
y-lowbit(y) + 1 <= j <= y.
在二维情况下,对应的更新和查询函数为:
for(int i = x; i <= N; i += lowbit(i))
for(int j = y; j <= N; j += lowbit(i))
C[x][y] += delta;
int Sum(int i, int j)
{
int result = 0;
for(int x = i; x > 0; x -= lowerbit(x))
{
for(int y = j; y > 0; y -= lowerbit(y))
{
result += C[x][y];
}
}
return result;
}
树状数组可以解决更新线段区间,查询某个点的问题。
在这种情况下,更新线段区间和查询点的时间复杂度仍然为O(logn).
设A[1,N]为我们要处理的数组。
另外设置一个数组B[1,N],使得B[1] + B[2] + .. B[i] = A[i], 其中1 <= i <= N.
即B[i] = A[i] - A[i-1]
当要查询A[i]的时候,我们只需在数组B上使用一般的树状数组操作查询区间[1,i]即可
当我们要给区间A[a~b]加上delta时,只需要在数组B上对B[a]进行一般树状数组的更新delta的操作,同时对数组B上对B[b+1]进行 - delta操作。
这种使用方式同样可以扩展到二维。
二维情况下,
B[i][j] = A[i][j] - A[i-1][j] - A[i][j-1]
当要查询A[i][j]的时候,只需在数组B上查询B[0][0]到B[i][j]的和即可。
当 要给矩形A[x1][y1],A[x2][y2]加上delta的时候,只需要在数组B上对B[x1][y1], [x2+1][y2+1]进行一般树状数组的更新delta的操作,同时对B[x1][y2+1], B[x2+1][y1]进行一般树状数组的更新-delta的操作即可