D DataForge 比赛工具站
DATA STRUCTURE LECTURE

树状数组:用 lowbit 拼出前缀和

树状数组 Fenwick Tree 用一个数组 tree[] 保存若干段区间和,让“单点修改”和“前缀查询”都能在 O(log n) 内完成。

核心公式 lowbit(x) = x & -x tree[i] 维护区间 [i-lowbit(i)+1, i]
01

先理解:tree[i] 到底维护哪一段?

lowbit(i) 表示二进制下最低位的 1 对应的值。它决定了 tree[i] 负责的区间长度。

例如 i=6,二进制是 110,所以 lowbit(6)=2,于是 tree[6] 维护 [5,6]

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

可视化:tree[i] 维护的区间

选择一个 tree[i],看它覆盖原数组中的哪一段。

lowbit(6) = 2,所以 tree[6] 维护原数组区间 [5, 6]。
6 的二进制是 110,最低位的 1 代表 2;tree[6] = a[5] + ... + a[6]。
当前高亮:tree[6] 覆盖 a[5] 到 a[6]。
02

单点加:为什么是 i += lowbit(i)?

a[x] 增加 k 时,所有“包含 x 的 tree 区间”都要更新。沿着 x, x+lowbit(x), ... 往上跳,刚好会经过这些负责区间。

void add(int x, int k) {
    for (int i = x; i <= n; i += lowbit(i)) {
        tree[i] += k;
    }
}

可视化:单点加更新哪些区间

每次访问的 tree[i] 都是一段包含 a[x] 的区间,所以这些节点都要加上 k

点击“下一步”或“自动演示” add(6, 3),观察哪些 tree 区间会一起变化。
tree[6] 维护 [5,6],tree[8] 维护 [1,8],它们都包含 a[6]。
更新路径:tree[6] -> tree[8]。
03

前缀查询:为什么是 i -= lowbit(i)?

查询 query(x) 时,我们把 [1,x] 拆成若干个 tree 已经维护好的区间。从 x 开始,每次拿走 tree[i] 负责的一段,然后跳到左边剩余部分。

int query(int x) {
    int ans = 0;
    for (int i = x; i; i -= lowbit(i)) {
        ans += tree[i];
    }
    return ans;
}

可视化:query 拆成哪些区间

查询前缀和时,访问到的每个 tree[i] 正好贡献一个不重不漏的区间。

点击“下一步”或“自动演示” query(6),观察前缀 [1,6] 如何被拆分。
从 i=6 开始,每次加入 tree[i],再令 i -= lowbit(i)。
query(6) 会拆成 [5,6] + [1,4]。
04

区间加 + 单点查询:维护差分数组

如果树状数组里维护的是差分数组 d[i] = a[i] - a[i-1],那么 a[x] 就等于 d[1] + ... + d[x]

于是给区间 [l,r] 整体加 k,只需要让 d[l] += k,再让 d[r+1] -= k。查询单点值时,直接求差分前缀和。

add(i, a[i] - a[i - 1]);
void range_add(int l, int r, int k) {
    add(l, k);
    add(r + 1, -k);
}
int point_query(int x) {
    return query(x);
}

可视化:区间加如何变成两个单点加

下面演示 [l,r] += k 如何只修改差分数组的两个位置,以及单点查询如何通过前缀和还原。

原数组 a[i]

差分数组 d[i]

树状数组 tree[i] 维护 d[]

区间 [3,6] += 2,会变成 add(3,2) 和 add(7,-2)。
点击“下一步”或“自动演示”,观察差分树状数组如何完成区间加和单点查询。
差分含义:a[x] = d[1] + ... + d[x],所以 point_query(x) = query(x)。