树状数组(Fenwick Tree)

什么是树状数组

树状数组(Binary Indexed Tree / Fenwick Tree)用数组高效维护前缀和,支持单点更新与前缀查询,均为 O(log n)。相比线段树,它代码极短、常数极小,是”只做前缀操作”场景下的首选;代价是功能更弱(不好直接做区间最值、区间修改)。

核心思想:二进制分解

关键定义:lowbit(i) = i & (-i),即 i 的二进制表示中最低位的 1 所代表的值。例如 lowbit(6) = lowbit(110₂) = 2

树状数组 tree[i] 存储原数组区间 [i - lowbit(i) + 1, i] 的和。任何前缀 sum[1..x] 都可以被分解成 O(log n) 个不重叠的 tree 区间

sum[1..x] = tree[x] + tree[x - lowbit(x)] + tree[x - lowbit(x) - lowbit(x - lowbit(x))] + ...

每次 x -= lowbit(x) 相当于把二进制中最低位的 1 抹掉,最多 log n 次就到 0,因此查询是 O(log n)。

正确性直观

  • 更新:修改 a[x] 后,要更新所有包含它的 tree 节点。从 x 开始不断 x += lowbit(x)——每步是”二进制进位”,恰好跳到所有覆盖区间,例如更新 x=1 会影响 1, 2, 4, 8…,更新 x=6 影响 6, 8…;
  • 查询x -= lowbit(x) 每次跳到下一个不重叠区间,保证前缀被完整、不重不漏地累加。

C++ 实现


#include <bits/stdc++.h>
using namespace std;

const int MAXN = 5e5 + 5;
int c[MAXN];
int n, m;

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

// 单点更新:a[pos] += value
void update(int pos, int value) {
    while (pos <= n) {
        c[pos] += value;
        pos += lowbit(pos);
    }
}

// 前缀和:sum[1..right]
int query(int right) {
    int sum = 0;
    while (right > 0) {
        sum += c[right];
        right -= lowbit(right);
    }
    return sum;
}

// 区间和:sum[left..right]
int rangeSum(int left, int right) {
    return query(right) - query(left - 1);
}

int main() {
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= n; i++) {
        int x; scanf("%d", &x);
        update(i, x);          // 构建:n 次 update,O(n log n)
    }
    while (m--) {
        int op, a, b;
        scanf("%d%d%d", &op, &a, &b);
        if (op == 1) update(a, b);              // 单点加
        else printf("%dn", rangeSum(a, b));    // 区间和
    }
    return 0;
}

复杂度

  • 单点更新 / 前缀查询:O(log n);
  • 初始化:n 次 update,O(n log n)(也可 O(n) 线性建树);
  • 空间:O(n)。

应用场景

  • 动态前缀和:频繁”改一个数 + 查一段和”的题目,模板题(洛谷 P3374)即是;
  • 逆序对计数:离散化后逐个插入统计,见《树状数组求逆序对》;
  • 单点改 + 区间查的差分扩展:可推广到”区间改 + 单点查”(用差分数组),再配合另一棵 BIT 实现”区间改 + 区间查”;
  • 需要求区间最值时不要用它——那是线段树/ST 表的领域。
滚动至顶部