首页 > 代码库 > 树状数组

树状数组

树状数组

本来以为特别难呢,没想到一点都不难。

技术分享

图大致就是这样,是不是很像一个树。。。

a数组是正常输入的数组,c数组就是要求的tree。

c[1]=a[1];c[2]=c[1]+a[2];

c[3]=a[3];c[4]=c[2]+c[3]+a[4];

c[5]=a[5];c[6]=c[5]+a[6];

c[7]=a[7];c[8]=c[4]+c[6]+c[7]+a[8];

而树状数组一般只有两种操作:

1.将某个数增加一个值。

2.查询某个区间的所有值的和。

我们会发现把某一个数的值增加后,之后和它相连所有点的值都要加,那怎么实现呢?

这时候就看出二进制的牛逼之处了,那就是使用lowbit。

例如:

把节点1增加一个值hahaha后,相应的,节点2,节点4,节点8都要增加。这时,我们使用lowbit就可以了。

lowbit(x)=x&-x;

1的二进制是0001,-1就是1111(一个数加一个负号是把这个数的二进制取反+1),他俩&一下,还是0001,也就是1。原来的1再加上求得的1,结果就是2。(这个例子不太成功,继续哈)

2的二进制是0010,-2就是1110,他俩&一下,还是0010,也就是2。原来的2再加上求得的2,结果就是4。(还是不行,再继续)

4的二进制是0100,-4就是1100,他俩&一下,还是0100,也就是4。原来的4再加上求得的4,结果就是8。(。。。。。。)

貌似整个例子都这样了,那就再随便来一个了:
7的二进制是0111,-7就是1001,他俩&一下,就是0001,也就是1。原来的7加上求得的1,结果是8,就是要增加的下一个节点啦。(终于对了)

所以,代码附上:

int add(int s,int num){
    while(s<=n){
        tree[s]+=num;
        s+=s&-s;
    }
}

然后是查询,其实也差不多,只不过是把原来的反过来就好了。原来是加上lowbit,现在改成减,往回推就好了。

查询一个区间的代码:

int read(int x,int y){
    x--;
    sum1=0;
    sum2=0;
    while(x){
        sum1+=tree[x];
        x-=x&-x;
    }
    while(y){
        sum2+=tree[y];
        y-=y&-y;
    }
    return sum2-sum1;
}

洛谷有两道树状数组的模板题:树状数组1和树状数组2。

第一道题比较正常:

1.将第x个数加上k。

2.输出区间[x,y]内每个数的和。

代码:

技术分享
 1 #include<cstdio>
 2 #include<iostream>
 3 using namespace std;
 4 int n,m,a,tree[500001],b,xx,yy,sum1,sum2;
 5 int add(int s,int num){
 6     while(s<=n){
 7         tree[s]+=num;
 8         s+=s&-s;
 9     }
10 }
11 int read(int x,int y){
12     x--;
13     sum1=0;
14     sum2=0;
15     while(x){
16         sum1+=tree[x];
17         x-=x&-x;
18     }
19     while(y){
20         sum2+=tree[y];
21         y-=y&-y;
22     }
23     printf("%d\n",sum2-sum1);
24 }
25 int main(){
26     scanf("%d%d",&n,&m);
27     for(int i=1;i<=n;++i){
28         scanf("%d",&a);
29         add(i,a);
30     }
31     for(int i=1;i<=m;++i){
32         scanf("%d%d%d",&b,&xx,&yy);
33         if(b==1)
34             add(xx,yy);
35         else
36             read(xx,yy);
37     }
38     return 0;
39 }
View Code

第二道题就恶心了:

1.将区间[x,y]内每个数加上k。

2.输出第x个数的值。

于是,我们就可以通过让每个数的之前所有区间的所有数的和代表这个数的值。(语文太差,没治了)

用实例解释一下:

第8个节点的值就是c[8]。第7个节点的值就是c[4]+c[6]+c[7]。

第6个节点的值就是c[4]+c[6]。第5个节点的值就是c[4]+c[5]。

第4个节点的值就是c[4]。第3个节点的值就是c[2]+c[3]。

第2个节点的值就是c[2]。第1个节点的值就是c[1]。

所以代码就变成了:

技术分享
 1 #include<cstdio>
 2 #define lowbit(x) x&-x
 3 int n,m,sum,x,y,z;
 4 int c[500001];
 5 void add(int x,int d){
 6     while(x<=n){
 7         c[x]+=d;
 8         x+=x&-x;
 9     }
10 }
11 int read(int x){
12     sum=0;
13     while(x){
14         sum+=c[x];
15         x-=x&-x;
16     }
17     printf("%d\n",sum);
18 }
19 int main(){
20     long long k;
21     scanf("%d%d",&n,&m);
22     for(int i=1;i<=n;i++){
23         y=x;
24         scanf("%d",&x);
25         add(i,x-y);
26     }
27     for(int i=1;i<=m;i++){
28         scanf("%d",&z);
29         if(z==1){
30             scanf("%d%d%d",&x,&y,&k);
31             add(x,k);
32             add(y+1,-k);
33         }
34         else{
35             scanf("%d",&x);
36             read(x);
37         }
38     }
39     return 0;
40 }
View Code

第一次写这么长的博客,累死。

另外,这位的博客写的不错,我也是通过这篇博客学会树状数组的。

树状数组