浅谈树状数组(解析+模板)

也不知道是什么时候开始,对于曾经学过的算法都不太用了

遇到区间修改,区间最值就知道用线段树,什么树状数组啊,st表啊都忘得差不多了

最近几次模考被卡翻了,于是又想起这些老朋友

来填个坑


 

首先我们要明确一点,树状数组只能维护求和,不能维护区间最值

树状数组利用了分治的思想,层数为logn,所以查询和修改都是logn,总复杂度为询问次数m乘logn

也就是mlogn,最关键的是和线段树比起来,常数要小得多,跑的飞快

而空间复杂度则是n的,只用开一维,还不用结构体

但是树状数组的应用范围也相对较小

通常分为两种

(1)单点修改+取件查询

(2)区间修改+单点查询

具体为什么,我们一会儿会说到

首先来张图片吧

这是比较流行的一种图

显而易见,树状数组是上面的C数组,而下面的A则是全数组,练出来的线代表每个节点的值是由那几个点组成的

例如:C[4]=C[2]+C[3]+A[3]

而我们如何找到组成当前节点的每一个点呢,或者说如何找到当前点的父亲呢

这就引出了我们今天的重中之重

lowbit函数

来看一下这个函数长什么样子

 

int lowbit(int x){return x&(-x);}

 

对的,只要压一下行就只有一行的小小的函数,就是整个树状数组的核心了

虽然短,但是蕴含的内容却很不好理解,这个函数所求的是x化为二进制之后从末尾开始一共有几个零

x加上这个数之后,就得到了他的父亲节点的下标

减去这个数之后,就得到了上一个与x的子树不相交的根节点(因为建立是就是这样定义的)

具体的原因与二进制中的补码有关,我们在这里就不详细说了,当个模板来背即可

例如上图中,6+2=8    6-2=4

而这两种不同的运算也就对应了树状数组中的两种操作

操作一:单点修改

首先我们可以知道当前要修改的点在原数组中的下标i,同时知道要加上(减去)的值v

根据lowbit函数的定义我们可以知道,包含原数组中的值的节点的下标不可能小于原数组的下标

同时改变某个点的值只会对其父亲有影响,所以理所应当的加上lowbit(i),直到根节点

对于经过的每个节点,将权值加上v

void build(int i,int v){for(;i<=n;i+=lowbit(i)) c[i]+=v;}

同样是压行之后只有一行

操作二:区间查询

和树状数组的含义有关,当前的树状数组中存的是类似于前缀和的东西

所以我们很难得到一段区间的值,但是我们可以知道从1到x的值

假设要查询的区间为[x,y],我们可以得到a[1]+a[2]+……+a[x-1],也可以同理得1到y

做一下差会可以了

具体的实现流程就是从当前点开始,不断减去lowit(i),知道节点1,将路径上的每一个点的值累加

特别一题,树状数组中的下标不能为0,否则lowbit函数就会炸掉

放一下操作代码(同样很简洁,这也是树状数组的优点之一)

int solve(int i){
    int sum=0;
    for(;i>=1;i-=lowbit(i)) sum+=c[i];
    return sum;
}

以上就是树状数组的单点修改和区间查询

来一道完整的题:树状数组模板1

附上AC代码:

#include<iostream>
#include<cmath>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<string>
#include<algorithm>
using namespace std;
inline int min(int a,int b){return a<b?a:b;}
inline int max(int a,int b){return a>b?a:b;}
inline int rd(){
    int x=0,f=1;
    char ch=getchar();
    for(;!isdigit(ch);ch=getchar()) if(ch=='-') f=-1;
    for(;isdigit(ch);ch=getchar()) x=x*10+ch-'0';
    return x*f;
}
inline void write(int x){
     if(x<0) putchar('-'),x=-x;
     if(x>9) write(x/10);
     putchar(x%10+'0');
     return ;
}
int n,m;
int c[500006];
int lowbit(int x){return x&(-x);}
void build(int i,int v){for(;i<=n;i+=lowbit(i)) c[i]+=v;}
int solve(int i){
    int sum=0;
    for(;i>=1;i-=lowbit(i)) sum+=c[i];
    return sum;
}
int main(){
    n=rd(),m=rd();
    for(int i=1;i<=n;i++){
        int x=rd();
        build(i,x);
    }
    for(int i=1;i<=m;i++){
        int f=rd();
        int x=rd(),y=rd();
        if(f==1) build(x,y);
        else write(solve(y)-solve(x-1)),puts("");
    }
    return 0;
}

 

然后就是一个小小的修改了

如何用树状数组来维护区间修改+单点查询

大家可以先自己想一想(反正我当时是没有想出来的)

不太会的同学不要担心

因为这里的树状数组和我们刚才讲的不太一样

哪里不一样呢,就是这里的C数组不是用来存和的

而是被用来存一个叫做差分的东西

什么是差分呢,就是对于一个数组

我们不维护每个地方的值,而是维护一个前缀和

使得从下标1加到下标x,就刚好可以得到原组的第x个元素

虽然查询变慢了,但是区间修改只需要O(1)的时间

为什么如此神奇呢,我们来举个例子

现在我们需要将2到4的区间加上1

我们就把差分数组下标为2的地方加1,下标为4+1的地方减1

就变成了:

计算前缀和,得到序列0 1 1 1 0 0 和原数组保持一致

是不是很神奇呢

而区间修改则是用树状数组来维护差分

虽然把修改变成了logn

但是相应的,单点查询也变成了logn

看似血亏,实则血赚

经过了上文的讲解,这里的具体操作我就不赘述了

再来一道题:树状数组模板2

附上AC代码:

#include<iostream>
#include<cmath>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<string>
#include<algorithm>
using namespace std;
inline int min(int a,int b){return a<b?a:b;}
inline int max(int a,int b){return a>b?a:b;}
inline int rd(){
    int x=0,f=1;
    char ch=getchar();
    for(;!isdigit(ch);ch=getchar()) if(ch=='-') f=-1;
    for(;isdigit(ch);ch=getchar()) x=x*10+ch-'0';
    return x*f;
}
inline void write(int x){
     if(x<0) putchar('-'),x=-x;
     if(x>9) write(x/10);
     putchar(x%10+'0');
     return ;
}
int n,m;
int c[500006];
int lowbit(int x){return x&(-x);}
void build(int i,int v){for(;i<=n;i+=lowbit(i)) c[i]+=v;}
int solve(int i){
    int sum=0;
    for(;i>=1;i-=lowbit(i)) sum+=c[i];
    return sum;
}
int main(){
    n=rd(),m=rd();
    int set=0;
    for(int i=1;i<=n;i++){
        int x=rd();
        build(i,x-set);
        set=x;
    }
    for(int i=1;i<=m;i++){
        int f=rd();
        if(f==1){
            int x=rd(),y=rd(),k=rd();
            build(x,k);build(y+1,-k);
        }
        else{
            int x=rd();
            write(solve(x)),puts("");
        }
    }
    return 0;
}

总而言之,树状数组还是很好的一种数据结构

只要利用得当,每一种数据结构都能够焕发出耀眼的光芒,给代码带来无限生机

posted @ 2018-10-21 21:37  Bruce--Wang  阅读(180)  评论(0编辑  收藏  举报