建议先阅读: L 线段树 SegmentTree


原理

树状数组(Binary Indexed Tree / Fenwick Tree)利用二进制的性质将数组划分为若干区间,使单点修改和前缀和查询均在 O(log n) 内完成。相比线段树,代码更短、常数更小。

核心概念

lowbit(x) = x & (-x),取 x 二进制最低位 1 所代表的值。

  • lowbit(6) = lowbit(110) = 2
  • lowbit(12) = lowbit(1100) = 4

BIT[i] 管理的区间为 [i - lowbit(i) + 1, i]。

操作逻辑

  • 前缀和 prefixSum(k): k -= lowbit(k) 直到 0,累加经过的 BIT 节点
  • 单点更新 add(k, delta): k += lowbit(k) 直到超过 n,更新 BIT 节点

区间管理示例

位置 ilowbit(i)管理区间
11[1, 1]
22[1, 2]
31[3, 3]
44[1, 4]
51[5, 5]
62[5, 6]
71[7, 7]
88[1, 8]

时间复杂度

操作复杂度
单点修改O(log n)
前缀和查询O(log n)
区间和查询O(log n)
O(n) 建树O(n)

空间: O(n)


实现

标准 BIT(单点修改 + 区间查询)

#include <stdlib.h>
 
#define LOWBIT(x) ((x) & -(x))
 
typedef struct {
    int* tree;
    int n;
} BIT;
 
void bit_init(BIT* b, int size) {
    b->n = size;
    b->tree = calloc(size + 1, sizeof(int));
}
 
void bit_destroy(BIT* b) {
    free(b->tree);
}
 
// O(n) 建树
void bit_build(BIT* b, const int* arr, int n) {
    b->n = n;
    free(b->tree);
    b->tree = calloc(n + 1, sizeof(int));
    for (int i = 1; i <= n; i++) {
        b->tree[i] += arr[i - 1];
        int parent = i + LOWBIT(i);
        if (parent <= n) b->tree[parent] += b->tree[i];
    }
}
 
void bit_add(BIT* b, int pos, int delta) {
    while (pos <= b->n) {
        b->tree[pos] += delta;
        pos += LOWBIT(pos);
    }
}
 
int bit_prefix_sum(BIT* b, int pos) {
    int sum = 0;
    while (pos > 0) {
        sum += b->tree[pos];
        pos -= LOWBIT(pos);
    }
    return sum;
}
 
int bit_range_sum(BIT* b, int l, int r) {
    return bit_prefix_sum(b, r) - bit_prefix_sum(b, l - 1);
}

区间修改 + 单点查询(差分 BIT)

typedef struct {
    int* tree;
    int n;
} DiffBIT;
 
void diff_bit_init(DiffBIT* b, int size) {
    b->n = size;
    b->tree = calloc(size + 1, sizeof(int));
}
 
void diff_bit_destroy(DiffBIT* b) { free(b->tree); }
 
static void diff_add(DiffBIT* b, int pos, int delta) {
    while (pos <= b->n) { b->tree[pos] += delta; pos += LOWBIT(pos); }
}
 
void diff_bit_range_add(DiffBIT* b, int l, int r, int val) {
    diff_add(b, l, val);
    diff_add(b, r + 1, -val);
}
 
int diff_bit_point_query(DiffBIT* b, int pos) {
    int sum = 0;
    while (pos > 0) { sum += b->tree[pos]; pos -= LOWBIT(pos); }
    return sum;
}

区间修改 + 区间查询(双 BIT)

typedef struct {
    long long* t1;   // diff[i]
    long long* t2;   // i * diff[i]
    int n;
} RangeBIT;
 
void range_bit_init(RangeBIT* b, int size) {
    b->n = size;
    b->t1 = calloc(size + 1, sizeof(long long));
    b->t2 = calloc(size + 1, sizeof(long long));
}
 
void range_bit_destroy(RangeBIT* b) { free(b->t1); free(b->t2); }
 
static void range_add_arr(long long* t, int n, int pos, long long delta) {
    while (pos <= n) { t[pos] += delta; pos += LOWBIT(pos); }
}
 
static long long range_sum_arr(long long* t, int pos) {
    long long s = 0;
    while (pos > 0) { s += t[pos]; pos -= LOWBIT(pos); }
    return s;
}
 
void range_bit_add(RangeBIT* b, int l, int r, long long val) {
    range_add_arr(b->t1, b->n, l, val);
    range_add_arr(b->t1, b->n, r + 1, -val);
    range_add_arr(b->t2, b->n, l, val * (l - 1));
    range_add_arr(b->t2, b->n, r + 1, -val * r);
}
 
long long range_bit_prefix_sum(RangeBIT* b, int pos) {
    return range_sum_arr(b->t1, pos) * pos - range_sum_arr(b->t2, pos);
}
 
long long range_bit_range_sum(RangeBIT* b, int l, int r) {
    return range_bit_prefix_sum(b, r) - range_bit_prefix_sum(b, l - 1);
}

权值 BIT 求第 K 小

typedef struct {
    int* tree;
    int n;
} KthBIT;
 
void kth_bit_init(KthBIT* b, int max_val) {
    b->n = max_val;
    b->tree = calloc(max_val + 1, sizeof(int));
}
 
void kth_bit_destroy(KthBIT* b) { free(b->tree); }
 
void kth_bit_add(KthBIT* b, int val, int delta) {
    for (int i = val; i <= b->n; i += LOWBIT(i))
        b->tree[i] += delta;
}
 
int kth_bit_kth(KthBIT* b, int k) {
    int pos = 0;
    for (int i = 20; i >= 0; i--) {
        int next = pos + (1 << i);
        if (next <= b->n && b->tree[next] < k) {
            k -= b->tree[next];
            pos = next;
        }
    }
    return pos + 1;
}

应用场景

  • 区间求和: 单点修改 + 区间和查询
  • 差分维护: 区间修改 + 单点查询(如区间加 k 后查询某位置的值)
  • 逆序对计数: 从右到左插入,查询比当前值小的已插入个数
  • 二维偏序(星星等级): 按 y 排序后,对 x 建立 BIT 统计

练习

题号题目难度知识点
P3374树状数组 1普及单点修改 + 区间查询
P3368树状数组 2普及区间修改 + 单点查询
P1908逆序对普及+树状数组求逆序对

力扣 (LeetCode) 有对应题型,竞赛方向推荐力扣/Codeforces。