树状数组-神奇的二进制

树状数组是解决快速更新以及统计数组某段区间总和,设一个数组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;

同理可以看出求和是通过减去最小进位数,得到下一个需要加的值。

原文地址:https://www.cnblogs.com/ACMessi/p/8469339.html