【算法模版】树状数组

136 阅读2分钟

1 定义

树状数组不是树,是数组。
树状数组不是树,是数组。
树状数组不是树,是数组。

按照名字去猜含义,树状数组是像树一样的数组,的确如此。更精确地描述如下文。

1.1 前置知识 - lowbit函数

记一个正整数x的二进制表示为

x=ak1ak2ak3...a2a1a0x = a_{k-1}a_{k-2}a_{k-3}...a_{2}a_{1}a_{0}

其中等于1的位为 ai1,ai2,...,aima_{i_1},a_{i_2},...,a_{i_m} 那么x可以二进制分解

x=2i1+2i2++2imx = 2^{i_1} + 2^{i_2} + \cdots + 2^{i_m}

我们设其中i1>i2>...>imi_1 > i_2 > ... > i_m 依据此,我们将区间 [1,x][1, x] 可以分成以下mm个区间

[1,2i1][2i1+1,2i1+2i2][2i1+2i2+1,2i1+2i2+2i3][2i1+2i2++2im1+1,2i1+2i2++2im1+2im]\begin{align*} & [1, 2^{i_1}] \tag{1} \\ & [2^{i_1} + 1, 2^{i_1} + 2^{i_2}] \tag{2} \\ & [2^{i_1} + 2^{i_2} + 1, 2^{i_1} + 2^{i_2} + 2^{i_3}] \tag{3}\\ & \cdots \\ & [2^{i_1} + 2^{i_2} + \cdots + 2^{i_{m - 1}} + 1, 2^{i_1} + 2^{i_2} + \cdots + 2^{i_{m - 1}} + 2^{i_m}] \tag{m} \end{align*}

对于上述每个区间,记区间右端点是RR,那么区间长度为RR的二进制分解下最小的2次幂。 结合例子来看,对于数字7,他的二进制分解为

7=22+21+207 = 2^2 + 2^1 + 2^0

那么区间[1,7][1, 7]就可以分解成

[1,22][22+1,22+21][22+21+1,22+21+20]\begin{align*} & [1, 2^2] \tag{1} \\ & [2^2 + 1, 2^2 + 2^1] \tag{2} \\ & [2^2 + 2^1 + 1, 2^2 + 2^1 + 2^0] \tag{3} \\ \end{align*}

[1,4] [5,6] [7,7][1, 4]\ [5, 6]\ [7, 7]三个区间。 这个给出的lowbit函数就是可以快速计算出x的最小二次幂的一个函数。 实现如下:

int LowBit(int x)
{
    return x & -x;
}

1.2 树状数组

树状数组就是基于上述切割数组的一种数据结构,主要作用是维护原数组的前缀和。

记原数组为A[1..N]A[1..N], 对应构建的树状数组为B[1..N]B[1..N],对于每个x,1xNx, 1 \leq x \leq N, 有:

B[x]=i=xlowbit(x)+1xA[i]B[x] = \sum_{i = x - lowbit(x) + 1}^xA[i]

B[x]B[x]表示区间[xlowbit(x)+1,x][x - lowbit(x) + 1, x]的A数组的和。 结合具体例子来看

树状数组.jpg

对于树状数组B[x]B[x],有如下性质:

    1. B[x]B[x]存放着所有以xx为根节点的子树中所有叶子节点的和
    1. B[x]B[x]的中子节点个数为lowbit(x)
    1. 除跟节点外,每个节点B[x]B[x]的父节点是B[x+lowbit(x)]B[x + lowbit(x)]
    1. 树的深度是O(logN)

2 实现

树状数组主要支持两个操作,分别是

  • 1 单点增加: 单点增加原始数组A的一个数,同时可以维护树状数组B;
  • 2 查询前缀和: 查询原始数组前多少项数之和。 其中单点增加可用来初始化树状数组。

2.1 单点增加

由上述分析可知,对于A[x]A[x], 只有B[x]B[x]以及其父节点存着A[x]A[x],那么A[x]A[x]变化时,只要递归向上更新B[x]B[x]即可。

// N is array length
// a[x] b[x]
void Add(int x, int y)
{
    int idx = x;
    while (idx <= N) {
        b[idx] += y;
        idx += LowBit(idx); // 父节点
    }
}

初始化树状数组就是将B[x]B[x]全部初始化0,每一位进行Add操作即可。

2.2 查询前缀和

B[x]B[x]存放的就是B[xlowbit(x)+1,x]B[x - lowbit(x) + 1, x]的和,递归向下求解即可。

int GetPreSum(int x)
{
    int sum = 0;
    int idx = x;
    while (idx >= 1) {
        sum += b[idx];
        idx -= LowBit(x);
    }
    return sum;
}

2.3 全部模板代码

const int N = 100010; // for example
int a[N];
int b[N];

int LowBit(int x)
{
    return x & -x;
}

void Add(int x, int y)
{
    int idx = x;
    while (idx <= N) {
        b[idx] += y;
        idx += LowBit(idx);
    }
}

int GetPreSum(int x)
{
    int sum = 0;
    int idx = x;
    while (idx >= 1) {
        sum += b[idx];
        idx -= LowBit(x);
    }
    return sum;
}