树状数组(学习笔记)

树状数组

树状数组的基本用途是维护序列的前缀和,相比前缀和数组,树状数组优势在于高效率的单点修改,单点增加(前缀和数组单点修改效率比较低)

因为树状数组的思想,原理还是很好理解的,就直接讲基本算法;

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

猜你喜欢

转载自www.cnblogs.com/PPXppx/p/10324232.html
今日推荐