1、作用:维护序列的前缀和,对于区间[1,x],树状数组将其分割成logx个子区间,从而快速询问区间和。
2、性质:
(1)每个内部节点c[x]保存以他为根的子树中所有叶节点的和。
(2)每个内部节点c[x]的子节点的个数等于lowbit(x)的大小
(3)出树根外,每个内部节点c[x]的父节点是c[x+lowbit(x)];
(4)树的深度为O(logN);
注意:树状数组只能处理下表为1~n的数,若出现0,则可能陷入lowerbit(0)=0的死循环。
3、树状数组的操作
(1)lowbit(x)
是表示n的最低位的1以及它后面的0构成的数值
eg:c[6],此时x=6;
6的二进制是110,
补码=反码+1 =010;
x&(-x)= 010;
所以返回2,表示移动的距离是2
int lowbit(int x)
{
return x&(-x);
}
(2)添加元素
对c[x]+y,由性质可知,每次只要移动lowbit(x)的长度即可。
void update(int x,int y)
{
while(x<=m)
{
c[x]+=y;
x+=lowbit(x);
}
}
(3)查询前缀和
将1-x分成logx个小区间,在每个小区间内查找元素
int sum(int x)
{
int res=0;
while(x>0)
{
res+=c[x];
x-=lowbit(x);
}
return res;
}
例题:
给定n个数列,规定有两种操作,一是修改某个元素,二是求子数列[a,b]的连续和。数列的元素个数最多100000个,询问操作最多100000次。
输入
第一行2个整数n,m(n表示输入n个数列,m表示有m个操作)
第二行输入n个数列。
接下来M行,每行有三个数k,a,b(k=0表示求子数列[a,b]的和,k=1表示第a个数列加b)
输出
输出若干行数字,表示每次K=0时对应输出一个子数列[a,b]的连续和。
输入样列
10 5
1 2 3 4 5 6 7 8 9 10
1 1 5
0 1 3
0 4 8
1 7 5
0 4 8
输出样例
11
30
35
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
const int MAX=1e5;
int n,m,a,b,k;
int c[MAX];
int lowbit(int x) //表示取出的非负整数x再二进制表示下最低位的1和它后面的0构成的值
{
return x&-x;
}
void update(int x,int v) //树状数组对某个元素的加法
{
while(x<=n)
{
c[x]+=v;
x+=lowbit(x);
}
}
int sum(int x) //查询前缀和
{
int res=0;
while(x>0)
{
res+=c[x];
x-=lowbit(x);
}
return res;
}
int main(void)
{
int v;
scanf("%d %d",&n,&m);
for(int i=1;i<=n;i++)
{
scanf("%d",&v);
update(i,v);
}
for(int i=1;i<=m;i++)
{
scanf("%d %d %d",&k,&a,&b);
if(k==0)
{
printf("%d\n",sum(b)-sum(a-1));
}
else update(a,b);
}
return 0;
}