树状数组是解决快速更新以及统计数组某段区间总和,设一个数组A[1-N],需要计算A[M-K]的总和,暴力解法需要O(K-M),如果我们求出sum(1-K)和sum(1-M),那么答案就是sum(1-M)-sum(1-K);
那么如何快速求出sum(1-N),可以考虑直接求,但如果我们再加一个条件,需要即时更新,那么使用暴力解法就会出现问题,假设更新A[K],需要对sum(1-K)到sum(1-N)进行更新,这是一个非常费时的过程。
这里我们使用一种二进制的思想,我们将十进制转为二进制进行举例思考,如上图,
C[0001] = A[0001];
C[0010] = A[0010] + A[0001];
C[0011] = A[0011];
C[0100] = A[0100] + A[0011] + A[0010] + A[0001];
C[0101] = A[0101];
C[0110] = A[0110] + A[0101];
C[0111] = A[0111];
C[1000] = A[1000] + A[0111] + A[0110] + A[0101] + A[0100] + A[0011] + A[0010] + A[0001];
把k定义为末尾连续0个数,可以理解为C[i] = A[i] + ... + A[i-2^k+1]。
更新的过程就是更新与A[i]相关的C[j],举例说明:i = 0001,j = 0001, 0010, 0100, 1000,i = 0101, j = 0110, 1000;不难看出有不断进位的规律。
求sum得过程就是求取每一位1所对应的数字的C[i]和,举例说明:
sum[1-1111] = C[1111] + C[1110] + C[1100] + C[1000];
C[1111] = A[1111];
C[1110] = A[1110] + A[1101];
C[1100] = A[1100] + A[1011] + A[1010] + A[1001];
C[1000] = A[1000] + A[0111] + A[0110] + A[0101] + A[0100] + A[0011] + A[0010] + A[0001];
了解了树状数组所具有的二进制规律,那么下来考虑如何用代码实现。
int lowbit(x) { return x&-x; } void update(int k,int x) { for(int i=k;i<=n;i+=lowbit(i)) { C[i]+=x; } }
0001 -> 0010, 即 0001 + 0001 = 0010;
0010 -> 0100, 即 0010 + 0010 = 0100;
0101 -> 0110, 即 0101 + 0001 = 0110;
0110 -> 1000, 即 0110 + 0010 = 1000;
lowbit函数就是求取所加的最小进位数,x&-x可以这样理解:-x表示补码,举例可证原码&补码等于最小进位数。
更新过程就是for循环从更新的k开始,将所有C数组中相关元素更新。
int getsum(int x) { int ans=0; for(int i=x;i;i-=lowbit(i))//i要大于0 ans+=C[i]; return ans; }
1111 -> 1110, 即 1111 - 0001 = 1110;
1110 -> 1100, 即 1110 - 0010 = 1100;
1100 -> 1000, 即 1100 - 0100 = 1000;
1000 -> 0000, 即 1000 - 1000 = 0000;
同理可以看出求和是通过减去最小进位数,得到下一个需要加的值。