树状数组的作用:
快速的对数列的一段范围求和
快速的修改数列的某一个数
为什么要使用树状数组:
大家从作用中看到快速求和的时候
可能会想到为什么不使用前缀和
只需要预处理一下就可以
在O(1)的时间复杂度下实行对于数列的一段范围的和
但是我们可以得到
当我们需要进行功能不仅含有
范围求和
还要求在同时对于
数列的某个数进行修改的时候
我们每次修改后还需要再求一次前缀和
这样的话时间复杂度最坏就达到了O(n)
所以我们就需要用到树状数组去实现这些功能
树状数组与线段树的区别:
他们之间的关系可以用包含关系来描述
也就是线段树包含了树状数组能够使用的功能
但树状数组的使用比线段树的使用快了很多倍
树状数组就像是一个专精的工具
效率高,但利用面相对于线段树不广。
树状数组的注意点以及图解:
注意点:
树状数组的下标为从1开始
对于树状数组的实现,我们通常采用一维数组来存储数列,其中奇数下标都为树状数组的第零层(也就是log2(lowbit(i))层),偶数下标为树状数组的log2(lowbit(i))层
提前创建两个数组:a[N]存原来给定的序列,tr[N]存构建的树状数组
构建的树状数组的详细图:
树状数组实现(3个核心函数):
函数一(lowbit()函数)
其中lowbit的作用是求二进制下最低位的1后面有多少个0
假如有k个0,那么就会返回2^k
具体实现:
int lowbit(int x){
return x&(-x);
}//&是二进制的每个数位“与”的结果
//例如 0&1=0 0&0=0 1&1=1
函数二(add()函数)
对于add函数他主要的作用就是
用来修改某一个位置上元素的值
当然我们在构建树状数组的时候
就可以用该函数去构建树状数组
具体实现:
void add(int x,int v){
for(int i=x;i<=n;i+=lowbit(i)) tr[i]+=y;
}
函数三(query()函数)
对于query函数他主要的作用就是
用来询问前i个元素的和
我们一般用query(r)-query(l-1)来表示[l,r]区间的和
具体实现:
int query(int x){
int sum=0;
for(int i=x;i>=1;i-=lowbit(i)) sum+=tr[i];
return sum;
}
题目:动态求连续区间和
题目详细:
代码详解:
#include<iostream>
using namespace std;
const int N=1e5+6;
int a[N],tr[N];
int n,m;
int lowbit(int x){
return x&(-x);
}
void add(int x,int v){
for(int i=x;i<=n;i+=lowbit(i)) tr[i]+=v;
}
int query(int x){
int res=0;
for(int i=x;i;i-=lowbit(i)) res+=tr[i];
return res;
}
int main(){
cin>>n>>m;
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
for(int i=1;i<=n;i++) add(i,a[i]);
while(m--){
int k,x,y;
scanf("%d%d%d",&k,&x,&y);
if(1==k) add(x,y);
else printf("%d\n",query(y)-query(x-1));
}
return 0;
}