树状数组
树状数组的基本用途是维护序列的前缀和,相比前缀和数组,树状数组优势在于高效率的单点修改,单点增加(前缀和数组单点修改效率比较低)
因为树状数组的思想,原理还是很好理解的,就直接讲基本算法;
1 lowbit函数
关于lowbit这个函数,可能会有点难以理解,但其实你不理解也没关系,把模板背下来就好
根据任意正整数关于2的不重复次幂的唯一分解性质,例如十进制21用二进制表示为10101,其中等于1的位是第0,2,4(最右端是第0位)位,即21被二进制分解成(2^4+2^2+2^0);
进一步地,整个区间[1,21]可以分成如下3个小区间:
长度为(2^4)的小区间[1,(2^4)];
长度为(2^2)的小区间[(2^4+1),(2^4+2^2)];
长度为(2^0)的小区间[(2^4+2^2+1),(2^4+2^2+2^0)];
对于给定的初始序列A,我们可以建立一个数组c,c[x]表示序列A的区间[x-lowbit(x)+1,x)]中所有数的和;
int lowbit(int x){return x&-x;}
2 单点增加操作
void update(int x,int y){
for(;x<=n;x+=lowbit(x))
c[x]+=y;
}
3 查询前缀和
int sum(int x){
int ans=0;
for(;x;x-=lowbit(x)) ans+=c[x];
return ans;
}
4 扩展
上述查询前缀和是统计[1,x]的前缀和,若要统计区间[x,y]的和,则调用sum函数即可:sum(y)-sum(x-1);
多维树状数组:
(扩充为m维)将原来的修改和查询函数中的一个循环,改成m个循环m维数组c中的操作;
以(n*m)的二维数组为例:
将(x,y)的值加上z,不是把区间[x,y]中的每个值加z
int update(int x,int y,int z){
int i=x;
while(i<=n){
int j=y;
while(j<=m){
c[i][j]+=z;
j+=lowbit(j);
}
i+=lowbit(i);
}
}
int sum(int x,int y){
int res=0,i=x;
while(i>0){
int j=y;
while(j>0){
res+=c[i][j];
j-=lowbit(j);
}
i-=lowbit(i);
}
return res;
}
注意树状数组的下标绝对不能为0,因为lowbit(0)=0,这样会陷入死循环
两道模板题,多打打模板~~
https://www.luogu.org/problemnew/show/P3374
https://www.luogu.org/problemnew/show/P3368