1,作用
树状数组支持单点修改和区间查询,且其代码量最小。
2,样式
由树状数组构建的新数组长度和原数组长度是一样的。
3,管辖区域
如图所示,c[1]管辖了a[1],c[2]管辖了a[1]+a[2],每一个c[n]都有其特定的管辖区域。
而这个管辖区域其实就是n转化为2进制后,从后往前看第一个1所代表的数。
eg:6转化为二进制是110,管辖区域就是1*2^1=2,如图,c[6]管辖了两个数字,a[5],a[6]。
当然,求管辖区域有特定的代码:
inline int lowbit(int x) {
return x & (-x);
}//&是一个位运算操作
4,建立树状数组
题目一般只给原始数组a[],要自己建立树状数组c[]
这时候有两种建树方式:
第一种是全部输入以后逆思维建树:
//建立树状数组
void build(int n) {
int k = 1; //从c[1]开始算
while (k <= n) {
for (int j = k; j > k - lowbit(k); j--) { //注意大于号
c[k] += a[j]; //先算出管辖区域,把管辖区域内所有a[i]相加
}
k++;
}
}
第二种是每输入一个数a[i],就把他的所有父亲节点都加上这个数:
//建立树状数组
void build(int i,int num) {
for (int j = i; j <= n; j += lowbit(j)) {
c[j] += num;
}
}
5,区间查询与单点修改
由上图可知,节点编号+区间长度=父亲编号,eg:c[4]节点的父亲是c[8],而4+lowbit(4)=8;,因此,我们可以通过x+lowbit(x)的方式求得父亲编号;
a, 单点修改
假如要将一个点的数值加上x,那么这个点的所有父亲节点都要加上x,这样才是维护了这个树状数组。
//p为节点,加上x
void add(int p, int x) {
while (p < N) {
c[p] += x;
p += lowbit(p);
}
}
b,区间查询
假如要求一段区间之和,例如,要求a[6]到a[8]之和,我们可以先求a[1]到a[8]之和,再减去a[1]到a[5]之和,就得到了结果;
下面是求a[1]到a[p]之和的代码
//区间之和
//从1到p之和
ll count(int p) {
ll result = 0;
while (p > 0) {
result += c[p];
p -= lowbit(p);
}
return result;
}
6,区间修改与单点查询
a,差分
区间修改想要不TLE,必须学会差分的思想:
数组a[]={1,3,4,6,7,10},那么差分数组(b[i]=a[i]-a[i-1] //a[0]=0)b[]={1,2,1,2,1,3}
如果a在2~4区间都加2,那么a[]={1,5,6,8,9,10},b[]={1,4,1,2,1,1}
所以,b数组的改变只是2处加2,4+1处减2,在2~4时因为前后同增同减,所以并不发生变化。
b,区间修改
7,模板题
a,洛谷p3374
对应5,单点修改与区间查询
AC代码:
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;
const int N=5e5+10;
int c[N];
int a[N];
//管辖区间
//对于节点x
inline int lowbit(int x) {
return x & (-x);
}
//建立树状数组
void build(int n) {
int k = 1;
while (k <= n) {
for (int j = k; j > k - lowbit(k); j--) {
c[k] += a[j];
}
k++;
}
}
//单点修改
//p节点,加上x
void add(int p, int x) {
while (p < N) {
c[p] += x;
p += lowbit(p);
}
}
//区间之和
//从1到p之和
ll count(int p) {
ll result = 0;
while (p > 0) {
result += c[p];
p -= lowbit(p);
}
return result;
}
int main() {
int n, m;
int p, x, y;
scanf("%d %d", &n, &m);
for (int i = 1; i <= n; i++) {
scanf("%d", &a[i]);
}
build(n);
for (int i = 1; i <= m; i++) {
scanf("%d %d %d", &p, &x, &y);
if (p == 1) {
add(x, y);
}
else {
printf("%lld\n", count(y) - count(x-1));
}
}
return 0;
}
b,洛谷p3368
AC代码如下
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;
const int N=5e5+10;
int c[N] = {0};
int a[N];
int n;
//管辖区间
//对于节点x
inline int lowbit(int x) {
return x & (-x);
}
//差分修改
//其余都是零,p及其父亲节点叠加一个+的操作
void add(int p, int x) {
while (p <=n) {
c[p] += x;
p += lowbit(p);
}
}
//区间之和
//从1到p之和
ll count(int p) {
ll result = 0;
while (p > 0) {
result += c[p];
p -= lowbit(p);
}
return result;
}
int main() {
int m;
int p, x, y,z;
scanf("%d %d", &n, &m);
for (int i = 1; i <= n; i++) {
scanf("%d", &a[i]);
}
for (int i = 1; i <= m; i++) {
scanf("%d", &p);
if (p == 1) {
scanf("%d %d %d", &x, &y, &z);
add(x, z);//大部分是0,区间左端叠加加法
add(y + 1, -z);//区间右端叠加减法(而且求count时只会算一次)
}
if (p == 2) {
scanf("%d", &x);
printf("%d\n", a[x] + count(x));
}
}
return 0;
}