2020-08-11
#Tech

【模板】树状数组1 & 学习笔记

树状数组


引入

lowbit的求解

lowbit(x)=x&(x)lowbit(x) = x \& (-x)

介绍

给定包含 nn 个数的数组 a1a_1 a2a_2 a3a_3 ...... ana_n。有两种操作:

查询区间 [l,r][l,r] 数的和。把 axa_x 增加 xx


分析

与线段树的区别与联系

我们知道线段树的区间是按照中间点划分的,而树状数组是根据 lowbitlowbit 来划分区间的。线段树的一个结点的区间是通过递归参数计算的,而树状数组的一个结点表示的区间可以根据结点标号计算出来,对于结点 ii,其表示的区间为 [ilowbit(i)+1,i][i − lowbit(i) + 1, i],然后我们再用一个数组 CC 表示每个结点对应的区间内的数的和。 下图为15个节点的树状数组: 灰色的结点是树状数组中的结点,每一层结点的 lowbitlowbit 相同。而每个灰色结点 ii 和其后面的白色长条就代表了结点 ii 表示的区间。


查询

若要查询区间 [l,r][l,r] 的和值,我们可以先求出 [1,r][1,r] 的和值,然后减去 [1,l1][1, l − 1] 的和值。现在问题就是怎么求 [1,x][1,x] 上的和值。实际上很简单

sum=0sum=0

加上区间 [xlowbit(x)+1,x][x−lowbit(x)+1,x] 的和值。

然后令 x=xlowbit(x)x=x−lowbit(x)

如果 x=0x=0 则退出,否则重复步骤 11

x=xlowbit(x)x=x−lowbit(x) 等价于将 xx 的二进制的最后一个 11 减去。而 xx 的二进制里最多有 logx\log x11,所以查询效率是 logx\log x 的。

例如查询 [1,11][1, 11] 的和。

sum=C[11]+C[10]+C[8]sum = C[11] + C[10] + C[8]

代码为:

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

更新

如果让 axa_x 增加 vv,那么只有对于包含位置 xx 的区间的和值才会受到影响。而求出哪些区间受影响有一个巧妙的操作,就是一直令 x=x+lowbit(x)x=x+lowbit(x),直到 x>nx>n,这一过程中所有的 xx 都是受影响的点。

如下图对应更新点 33

void update(int x, int v) {
     for (int i = x; i <= n; i += lowbit(i)) {
         C[i] += v;
    }
}

这一步的复杂度也是 O(logn)O(\log n)


性质

为什么树状数组只用来求区间和值?

因为树状数组只能求出区间 [1,x][1,x] 的信息,用来求区间和只是通过前缀和转换了一下,所以如果是求区间最大(最小)值,就只能求区间 11xx 了,因为最大(最小)不满足加减的性质。

树状数组的比较方便,代码量小,并且常数较小。

数组数组能解决的问题是线段树的子集,线段树能解决树状数组不能解决的问题。

树状数组为什么没有建树,因为初始的时候,依次更新 nn 个元素代替了建树。当然线段树也可以这么做。

CC BY-SA 4.0

This article is licensed under CC BY-SA 4.0. You are free to share and adapt this work, provided you attribute Kevin Zhong and distribute under the same license.