树状数组

· · 个人记录

树状数组的用处

快速求和+快速修改,可以理解为动态前缀和

建立

这就是树状数组能快速求解信息的原因:我们总能将一段前缀 [1, n] 拆成 不多于 \boldsymbol{\log n} 段区间,使得这 \log n 段区间的信息是 已知的。

于是,我们只需合并\log n 段区间的信息,就可以得到答案。相比于原来直接合并 n 个信息,效率有了很大的提高。

不难发现信息必须满足结合律,否则就不能像上面这样合并了。

树状数组的工作原理

对于树状数组每一个元素c_i

以下式子成立:

C_i=\sum\limits_{x=i-lowbit(i)+1}^{i} lowbit函数
int lowbit(int x){
    return x&(-x);
}

查询

c[x] 开始往前跳,有 c[x] 管辖 a[x-\operatorname{lowbit}(x)+1 \ldots x]; 令 x \gets x - \operatorname{lowbit}(x),如果 x = 0 说明已经跳到尽头了,终止循环;否则回到第一步。 将跳到的 c_x 合并,即为[1,x]的区间和。

int getsum(int x){
    int sum=0;
    for(int i=x;i>0;i-=lowbit(i)) sum+=c[i];
    return sum;
}

修改

管辖 a[x]c[y] 一定包含 c[x],所以 y 在树状数组树形态上是 x 的祖先。因此我们从 x 开始不断跳父亲,直到跳得超过了原数组长度为止。

n 表示 a 的大小,不难写出单点修改 a[x] 的过程:

初始令 x' = x。 修改 c[x']。 令 x' \gets x' + \operatorname{lowbit}(x'),如果 x' > n 说明已经跳到尽头了,终止循环;否则回到第二步。

实现

void modify(int x,int d){
    for(int i=x;i<=n;i+=lowbit(i)){
        c[i]+=d;
    }
}

例题

P3374 【模板】树状数组 1

P3368 【模板】树状数组 2(维护差分)