树状数组 (BIT)
💡 核心思想
树状数组(Binary Indexed Tree, BIT)用于高效处理前缀查询和单点修改。核心是 lowbit 运算(取最低位1代表的值),用 $O(\log n)$ 完成单点修改和前缀求和。常用于求逆序对、动态前缀和等。
树状数组的精髓是"分块"思想——用 lowbit 将前缀拆成若干段。$tree[i]$ 管辖区间 $[i - lowbit(i) + 1, i]$。理解了这个,修改和查询的代码就自然理解了。树状数组代码极短(核心不到10行),比线段树简洁得多,但功能也相对有限。
🎯 直觉理解
想象你要维护一组数的"从开头到第 k 个的和"。朴素做法每次 $O(k)$。树状数组就像把前缀分成若干段预先算好的区间,查询时只需要合并 $\log n$ 段。就像你有不同面额的硬币,用最少的硬币凑出任意金额。
📝 算法流程
- lowbit(x) = x & (-x)
- 修改:从位置 i 开始,i += lowbit(i) 一直往上
- 查询前缀和:从位置 i 开始,i -= lowbit(i) 一直往下
- 可扩展为区间修改+区间查询
$$lowbit(x) = x \& (-x)$$
$$tree[i] = \sum_{j=i-lowbit(i)+1}^{i} a[j]$$
📊 复杂度分析
| 指标 | 复杂度 |
|---|---|
| 时间 | $O(\log n)$ 每次操作 |
| 空间 | $O(n)$ |
💻 参考实现(C++)
C++ (C++17)
#include <bits/stdc++.h>
using namespace std;
int n;
long long tree[500005];
int lowbit(int x) { return x & (-x); }
void add(int i, long long v) { for (; i <= n; i += lowbit(i)) tree[i] += v; }
long long sum(int i) { long long s = 0; for (; i > 0; i -= lowbit(i)) s += tree[i]; return s; }
int main() {
int m; cin >> n >> m;
for (int i = 1; i <= n; i++) { long long x; cin >> x; add(i, x); }
while (m--) {
int op; cin >> op;
if (op == 1) { int i; long long v; cin >> i >> v; add(i, v); }
else { int l, r; cin >> l >> r; cout << sum(r) - sum(l-1) << "\n"; }
}
return 0;
}⚠️ 常见坑点
下标必须从 1 开始(0 的 lowbit 是 0 会死循环)
long long 防溢出
区间修改需要差分技巧(树状数组维护差分数组)
求逆序对时需要先离散化
📚 相关题目
| 题目 | 来源 | 难度 | 备注 |
|---|---|---|---|
| P3374 树状数组 1 | 洛谷 | CSP-S | BIT模板 |
| P3368 树状数组 2 | 洛谷 | CSP-S | 区间修改+单点查询 |
| P1908 逆序对 | 洛谷 | CSP-S | BIT求逆序对 |