算法竞赛基础:树状数组
是什么?
树状数组虽然语义上是树状,但是实际上还是一个数组。
树状数组的功能就是单点和区间的修改和查询。
例如,如果想增加一个点的值,那么你需要让其上方所有能对齐的树状数组c全部增加相同的值,在查询包含该数组的区间时,从区间最右端端点处可以访问,将所有小于c[i]
且len大于等于c[i]`的数组数组相加,就是要查询的值。
例如,当我们在a[6]
上增加2
的时候,向上对齐的数组c[6]
,c[8]
,c[16]
同样增加2
如果我们想要查询1到7的区间,则需要从最右端开始,也就是c[7]
(图中未标出,为0111的位置),加上c[6]
,c[4]
所得值就是结果。
为什么?
如果向右和向左访问呢?树状数组利用的是二进制的性质,如果我们想访问i + 1
,那么我们应该让 i += lowbit(i)
,其中lowbit(i)
是关于i
的一个位运算:
lowbit(i) = i & -i
这样做会只留下i
的二进制最右边的1,例如:
i = 00110110,经过这样的运算之后,i会变成:00000010
同理,向左访问树状数组时,通常让i -= lowbit(i)
怎么做?
这里列出树状数组的单点修改和求和模板代码:
// lowbit函数需要自己写
int lowbit(int x) {return x & -x;
}// 单点修改
void update(ll k, ll x) {for (int i = k; i <= n; i += lowbit(i)) t[i] += x;return ;
}// 求和
void getsum(int k) {int ans = 0;for (int i = k; i > 0; i -= lowbit(i)) ans += t[i];return ans;
}
模板题
样例输入:
5 4
1 2 3 4 5
1 1 1
2 1 2
1 4 2
2 3 4
样例输出:
4
9
题解代码
#include <bits/stdc++.h>
using namespace std;
using ll = long long;const int MAX_N = 2e5 + 100;// t是树状数组
ll a[MAX_N], t[MAX_N];
int n, q;int lowbit(int x) {return x & -x;
}void update(int k, ll x) {for (int i = k; i <= n; i += lowbit(i)) t[i] += x;return ;
}ll getsum(int k) {ll res = 0;for(int i = k; i > 0; i -= lowbit(i)) res += t[i];return res;
}void solve() {cin >> n >> q;// 读入数据,并且时刻更新树状数组for (int i = 1; i <= n; i++) cin >> a[i];for (int i = 1; i <= n; i++) update(i, a[i]);// 读状态while (q--) {int op; cin >> op;if (op == 1) {// 单点修改ll k, v; cin >> k >> v;update(k, v);} else {// 区间查询ll l, r; cin >> l >> r;cout << getsum(r) - getsum(l - 1) << '\n';}}return ;
}int main() {solve();return 0;
}