『树状数组』树状数组模板
竞赛需要用到的点
- 树状数组可以解决大部分基于区间上更新、查询的操作
- 树状数组能写的线段树都能写,但线段树能写的树状数组不一定能写
- 代码量小,且常数比线段树小
- 树状数组是 树与二进制 的混合使用
- lowbit(x) -> x&(-x) 为何? -x = x的二进制数中 0 变 1,1 变 0 然后末尾 + 1 lowbit可以得到一个由原二进制数变来的只有末尾1的新的二进制数
树状数组略讲
此处借用 百度百科 的图片
由此图我们可以得到以下信息(所有信息均用二进制处理)
-
所有区间所包含的点的数量为 (2^k) 其中 (k) 为该数二进制下末尾 (0) 的个数
-
[单点修改] 当我们修改一个点 (i) 的值后,那么所包含 (i) 的区间都要改变
- 哪些值要变? (i) 在二进制下,加上该二进制下最低位1 例如(1001 -> 1010)(9 -> 10)
-
[单点查询] 因为树状数组存储的形式类似于前缀和的形式,所以单点查询的办法也显而易见
- 如何查询?查询 (b_i) 与 (b_{i - 1}) 的值,然后两式相减 可得 (a_i)
-
[区间查询] 和单点查询类似 若查询([i, j]) ,那么可得 (b_j - b_{i - 1})
-
[区间更新] 对于这种问题我们分两种情况
-
[区间修改&单点查询] 这里我们需要用到差分的思想,即将原本的树状数组的数组用差分数组来表示 (b_i = a_i - a_{i - 1}) ,答案也呼之欲出,对于差分 我们每次想得到 (a_n) 都是 (Sigma_{i = 1}^{n}b_i) 然后对于每次修改,我们对 (b_i) 与 (b_j + 1) 进行单点修改即可。
-
[区间修改&区间查询] 这里我们也要用到差分的思想,但这次我们需要维护两个差分数组((sum1) 和 (sum2))为什么? 我们从上面可以得到,想要知道 (a_{[i, j]}) 的和就得用以下式子求出,(Sigma_{i = 1}^na = Sigma_{i = 1}^nSigma_{j = 1}^ib_j) 激动人心的化简时刻到了$$egin{eqnarray}Sigma_{i = 1}^na &=& Sigma_{i = 1}^nSigma_{j = 1}^ib_j &=& n imes b_1+(n-1) imes b_2 + (n - 2) imes b_3 + ... + b_n &=& nSigma_{i = 1}^nb_i - b_2-2b_3-3b_4-...-(n-1)b_n&=&nSigma_{i = 1}^nb_i + Sigma_{i = 2}^nSigma_{j = 1}^{i - 1}(-b_i)&=&nSigma_{i = 1}^nb_i + Sigma_{i = 2}^n(-b_i) imes (i - 1)&=&nSigma_{i = 1}^nb_i - Sigma_{i = 2} ^ nb_i imes (i - 1)end{eqnarray}$$
因为 (Sigma_{i = 2}^nb_i imes (i - 1)) 与 (Sigma_{i = 1}^nb_i imes (i - 1)) 等价
所以原式子 = (nSigma_{i = 1}^nb_i - Sigma_{i = 1}^nb_i imes (i - 1)) 在这个式子中,由于有(Sigma_{i = 1}^n)所以我们可以易得用差分,用两个差分数组维护 (b_i) 与 (b_i imes (i - 1)) 就可以了
-
部分模板代码展现
- 单点修改 单点查询
#define fre yes
#include <cstdio>
const int N = 100005;
int a[N], b[N];
int n;
int lowbit(int x) {
return x & (-x);
}
void point_change(int x, int k) { // point_change(x, k)
while(x <= n) {
b[x] += k;
x += lowbit(x);
}
}
int getSum(int x) { // getSum(x) - getSum(x - 1)
int res = 0;
while(x > 0) {
res += b[x];
x -= lowbit(x);
} return res;
}
int main() {
...
}
- 单点修改 区间查询
#define fre yes
#include <cstdio>
const int N = 100005;
int a[N], b[N];
int n;
int lowbit(int x) {
return x & (-x);
}
void point_change(int x, int k) { // point_change(x, k)
while(x <= n) {
b[x] += k;
x += lowbit(x);
}
}
int getSum(int x) { // getSum(r) - getSum(l - 1)
int res = 0;
while(x > 0) {
res += b[x];
x -= lowbit(x);
} return res;
}
int main() {
...
}
- 区间修改 单点查询
#define fre yes
#include <cstdio>
const int N = 100005;
int a[N], b[N];
int n;
int lowbit(int x) {
return x & (-x);
}
void interval_change(int x, int k) { // point_change(l, x) point_change(r + 1, -x)
while(x <= n) {
b[x] += k;
x += lowbit(x);
}
}
int getSum(int x) { // getSum(x)
int res = 0;
while(x > 0) {
res += b[x];
x -= lowbit(x);
} return res;
}
int main() {
...
}
- 区间查询 区间修改
#define fre yes
#include <cstdio>
const int N = 100005;
int sum1[N], sum2[N];
int n;
int lowbit(int x) {
return x & (-x);
}
void interval_change(int j, int k) {
// push -> interval_change(i, a[i] - a[i - 1])
// change -> interval_change(l, k) interval_change(r + 1, -k)
int i = j;
while(j <= n) {
sum1[j] += k;
sum2[j] += k * (i - 1);
j += lowbit(j);
}
}
int getSum(int i) { // getSum(r) - getSum(l - 1)
int res = 0, x = i;
while(i > 0) {
res += x * sum1[i] - sum2[i];
i -= lowbit(i);
} return res;
}
int main() {
...
}