简介:
树状数组是一种维护前缀和、区间和的数据结构。它在原数组上添加索引,从而高效维护数据。 神奇而小巧的数据结构,树状数组的下标必须从1开始!无论是他的修改还是查询,时间复杂度永远都是 logn,适合用于做区间和,前缀和,单点修改比前缀和标准做法的修改快一些
原理:
树状数组引入了分级管理制度,设置一个管理小组,每个管理员管理一个或多个连续的元素。 例如,数列有16个元素,分别用a[1],a[2],…,a[16]存储,管理数组为c[]。管理数组c[]是树状的,因 此称为树状数组
索引:
lowbit:将一个数转化为他的二进制,返回二的二进制下原数的末尾零次方,一个数的lowbit值就是他索引的数据个数
eg:
lowbit(5):因为十进制下的5转为二进制为101,末尾零个个数为零 , 所 以返回2的零次方,也就是一。
在计算机中二进制数采用的是补码表示, i的补码正好是i取反加1。 c[i]存储的区间长度:lowbit(i)=(-i)&i。
lowbit代码实现:
int lowbit(int x)
{
return x&-x;
}
既然树状数组是通过二进制分解划分区间,那么c[i]存储的是哪些值?
1)区间长度 若i的二进制表示末尾有k个连续的0,则c[i]存储的区间长度为2 k,从a[i] 向前数2 k个元素,即c[i]=a[i-2k+1]+a[i-2k+2]+…+a[i]。
区间长度就是i的二进制表示下最低位的1及它后面的0构成的数值。 例如i=20,其二进制表示为10100,末尾有2个0,区间长度为2 2,其实就是 10100最低位的1及其后面的0构成的数值(100)2 ,十进制为4
2)前驱和后继 直接前驱:c[i]的直接前驱为c[ilowbit(i)],即c[i]左侧紧邻的子树的根。 直接后继:c[i]的直接后继为c[i+lowbit(i)],即c[i]的父节点。 前驱:c[i]左侧所有子树的根。 后继:c[i]的所有祖先。![](https://i-blog.csdnimg.cn/blog_migrate/9cdd5e28de4e0c061b82cc57f58f5e69.png)
3)查询前缀和 前i个元素的前缀和sum[i]等于c[i]加上c[i]的前驱。 sum[7]等于c[7]加上c[7]的前驱,sum[7]=c[7]+c[6]+c[4]。
4)点更新 若对a[i]进行修改,令a[i]加上一个数z,则只需更新c[i]及其后继(祖先), 即令这些节点都加上z即可,无需修改其他节点。 例如,修改a[5],令其加2。 只需c[5]+2,然后c[5]的后继分别加2, 即c[6]+2、c[8]+2。
5)查询区间和 若求区间和值a[i]+a[i+1]+…+a[j],则求解前j个元素的和值减去前i1个元素 的和值即可,即sum[j]-sum[i-1]。
算法实现:
树状数组的优点是实现简单,效率高省空间。主要用于查询前缀和、区间和及点更新,对点查询、 区间更新效率较低。
前缀和:求a[1]到a[i]的前缀和,普通数组O(n),树状数组O(logn)。
区间和:求a[i]到a[j]的区间和,普通数组O(n),树状数组O(logn)。 点更新:修改a[i]加上z,普通数组O(1),树状数组O(logn)。 点查询:查找第i个元素,普通数组 O(1),树状数组O(logn) (求sum[i]-sum[i-1])。
区间更新:区间a[i]到a[j]的所有元素加z,普通数组O(n) ,树状数组O(nlogn) 。
减法规则:当问题满足减法规则时,例如求区间和a[i]到a[j],则sum(i,j)=sum[j]-sum[i-1]。
局限1:维护信息需要满足减法规则,所以无法直接维护区间最值。
局限2:对修改支持不好,单点修改可以求差值,转换为加法操作
贴一下树状数组模板代码:
#include<bits/stdc++.h>
using namespace std;
int c[500001]={0},n;
int m,a,x,y,z;
int lowbit(int x)
{
return x&-x;
}
void smart(int x,int z)
{
for(;x<=n;x+=lowbit(x))
{
c[x]+=z;
}
}
int body(int x)
{
int ans=0;
for(;x;x-=lowbit(x))
{
ans+=c[x];
}
return ans;
}
int main()
{
cin>>n>>m;
for(int i=1;i<=n;i++)
{
scanf("%d",&a);
smart(i,a);
}
while(m--)
{
scanf("%d%d%d",&z,&x,&y);
if(z==1)
{
smart(x,y);
}
if(z==2)
{
cout<<body(y)-body(x-1)<<endl;
}
}
return 0;
}
看的这么认真,不点个赞再走吗?