首页 > 编程语言 > 详细

「算法笔记」树状数组

时间:2020-03-27 12:18:55      阅读:70      评论:0      收藏:0      [点我收藏+]

一、引言

主要是想存个树状数组的板子顺便orz一下dyls技术分享图片

在解题过程中,我们有时需要维护一个数组的前缀和S[i]=A[i]+A[2]+...+A[i]。但是不难发现,如果我们修改了任意一个A[i],S[i],S[i+1]...S[n]都会发生变化。可以说,每次修改A[i]后,调整前缀和S在最坏的情况下会需要O(n)的时间。当n非常大时,就GG啦。树状数组的修改与求和都是O(logn),效率较高。(但是对于dyls来说就非常非常慢啦,一般来说dyls会使用O(1)的dy算法技术分享图片

二、基本算法

根据任意正整数关于2的不重复次幂的唯一分解性质,若一个正整数x的二进制表示为10101,其中等于1的位是0,2,4,则正整数x可以被“二进制分解”成24+22+20。进一步地,区间[1,x]可以分成O(logx)个小区间:

  1.长度为24的小区间[1,24]

  2.长度为22的小区间[24+1,24+22]

  3.长度为20的小区间[24+24+1,24+22+20]

对于区间 [1,x],树状数组将其分为 logx 个子区间,从而满足快速询问区间和。

这些子区间的共同特点是:若区间结尾为R,则区间长度就等于R的“二进制分解”下最小的2的次幂,我们设为 lowbit(R)。对于给定的序列a,我们建立一个数组 f,其中f[x]保存序列a的区间[x-lowbit(x)+1,x]中所有数的和。

(摘自《信息学奥赛一本通》)

1.求 lowbit(x)

lowbit(x)表示取出非负整数x在二进制表示下最低位的1以及它后边的0构成的数值。

如何得出 lowbit(x)=x&(-x)?

设x>0,x的第k为是1,第0~k-1位都是0。

为了实现 lowbit 运算,先把x取反,此时第k位变为0,第0~k-1位都是1。再令 x=x+1,此时因为进位,第k为变为1,第0~k-1位都是0。在上面的取反加1操作后,x的第k+1到最高位恰好与原来相反,所以 x&(~x+1) 仅有k位为1,其余位都是0。而在补码表示下,~x=-1-x,因此 lowbit(x)=x(~x+1)=x&(-x)。

技术分享图片 

2.对某个元素进行加法操作

即给序列中的某个数 a[x]加上y。只有结点 f[x] 及其所有祖先结点保存的“区间和”包含a[x],而任意一个结点的祖先至多只有logN个,我们逐一对它们的数组f值进行更新即可。(莫名压行)

技术分享图片

3.查询前缀和

即求序列a第1~写个数的和。我们可以求出x的二进制表示中每个等于1的位,把[1,x]分成 O(logN) 个小区间,而每个小区间的区间和都已经保存在数组f中。

技术分享图片

如果要查询序列a的区间 [l,r] 中所有数的和,只需计算 get(r)-get(l-1)。

4.多维树状数组

一维树状数组的每个操作的复杂度都是O(logn)的。塔可以扩充为m维,这样每个操作的复杂度就变成了O(logmn),在m不大的时候,时间复杂度是可以接受的。扩充的方法就是将原来的修改和查询函数中的一个循环,改成m个循环m维数组f中的操作。就是说,如果有n×m的二维数组a,树状数组为f,那么可以如下操作(只有二维树状数组的板子):

 技术分享图片

6.注意事项

要注意树状数组能处理的是下标为1...n的数组,不能出现下标为0的情况。因为 lowbit(0)=0,会陷入死循环然后GG。

 三、模板

「树状数组1」单点修改,区间查询

题目链接

这是一道模板题。

给定数列a[1],a[2],...,a[n],你需要依次进行q个操作,操作有两类:

1 i x:给定 i,x,将 a[i] 加上x;
2 l r:给定 l,r,求 的值$\sum_{i=l}^r{a[i]}$(换言之,求 a[l]+a[l+1]+...+a[r] 的值)。

here 按照上述做法写就阔以AC啦。

 1 #include<bits/stdc++.h>
 2 #define lowbit(x) x&(-x)
 3 #define int long long
 4 using namespace std;
 5 const int N=1e6+5;
 6 int n,q,f[N],opt,x,y;
 7 void put(int x,int y){
 8     for(;x<=n;x+=lowbit(x)) f[x]+=y;
 9 }
10 int get(int x){
11     int res=0;
12     for(;x;x-=lowbit(x)) res+=f[x];
13     return res;
14 }
15 signed main(){
16     //freopen(".in","r",stdin);
17     //freopen(".out","w",stdout);
18     scanf("%lld%lld",&n,&q);
19     for(int i=1;i<=n;i++)
20         scanf("%lld",&x),put(i,x);
21     while(q--){
22         scanf("%lld%lld%lld",&opt,&x,&y);
23         if(opt==1) put(x,y);
24         else printf("%lld\n",get(y)-get(x-1));
25     }
26     return 0;
27 } 

 

「树状数组2」区间修改,单点查询

题目链接

这是一道模板题。

给定数列a[1],a[2],...,a[n],你需要依次进行q个操作,操作有两类:

1 l r x:给定 l,r,x,对于所有 i∈[l,r],将a[i]加上x(换言之,将 a[l]+a[l+1]+...+a[r] 分别加上x)
2 i:给定i,求a[i]的值。

here 可参考这里

 1 #include<bits/stdc++.h>
 2 #define lowbit(x) x&(-x)
 3 #define int long long
 4 using namespace std;
 5 const int N=1e6+5;
 6 int n,q,f[N],opt,x,y,k;
 7 void put(int x,int y){
 8     for(;x<=n;x+=lowbit(x)) f[x]+=y;
 9 }
10 int get(int x){
11     int res=0;
12     for(;x;x-=lowbit(x)) res+=f[x];
13     return res;
14 }
15 signed main(){
16     //freopen(".in","r",stdin);
17     //freopen(".out","w",stdout);
18     scanf("%lld%lld",&n,&q);
19     for(int i=1;i<=n;i++)
20         scanf("%lld",&x),put(i,x-y),y=x;
21     while(q--){
22         scanf("%lld",&opt);
23         if(opt==1) scanf("%lld%lld%lld",&x,&y,&k),put(x,k),put(y+1,-k);
24         else scanf("%lld",&x),printf("%lld\n",get(x));
25     }
26     return 0;
27 } 

 

「树状数组3」区间修改,区间查询

题目链接

这是一道模板题。

给定数列a[1],a[2],...,a[n],你需要依次进行q个操作,操作有两类:

1 l r x:给定 l,r,x,对于所有 i∈[l,r],将a[i]加上x(换言之,将 a[l]+a[l+1]+...+a[r] 分别加上x)

2 l r:给定 l,r,求 的值$\sum_{i=l}^r{a[i]}$(换言之,求 a[l]+a[l+1]+...+a[r] 的值)。

here 可参考这里

建立两个树状数组f0,f1,起初全部赋值为零。对于每条指令“1 l r x”,执行4个操作:

1.在树状数组f0中,把位置l上的数加x。

2.在树状数组f0中,把位置r+1上的数减x。

3.在树状数组f1中,把位置l上的数加l*x。

4.在树状数组f1中,把位置r+1上的数减(r+1)*x。

另外,我们建立数组sum存储序列a的原始前缀和。对于每条指令“2 l r”,拆分成 1~r 和 1~l-1两部分,二者相减。写成式子就是:

$(sum[r]+(r+1)*get(f_0,r)-get(f_1,r))-(sum[l-1]+l*get(f_0,l-1)-get(f_1,l-1))$

 1 #include<bits/stdc++.h>
 2 #define lowbit(x) x&(-x)
 3 #define int long long
 4 using namespace std;
 5 const int N=1e6+5;
 6 int n,q,f[2][N],sum[N],opt,l,r,x,ans;
 7 void put(int k,int x,int y){
 8     for(;x<=n;x+=lowbit(x)) f[k][x]+=y;
 9 }
10 int get(int k,int x){
11     int res=0;
12     for(;x;x-=lowbit(x)) res+=f[k][x];
13     return res;
14 }
15 signed main(){
16     //freopen(".in","r",stdin);
17     //freopen(".out","w",stdout);
18     scanf("%lld%lld",&n,&q);
19     for(int i=1;i<=n;i++)
20         scanf("%lld",&x),sum[i]=sum[i-1]+x; 
21     while(q--){
22         scanf("%lld%lld%lld",&opt,&l,&r);
23         if(opt==1) scanf("%lld",&x),put(0,l,x),put(0,r+1,-x),put(1,l,l*x),put(1,r+1,-(r+1)*x);
24         else{
25             ans=sum[r]+(r+1)*get(0,r)-get(1,r);
26             ans-=sum[l-1]+l*get(0,l-1)-get(1,l-1);
27             printf("%lld\n",ans); 
28         }
29     }
30     return 0;
31 } 

 

「二维树状数组1」单点修改,区间查询

题目链接 

给出一个n×m的零矩阵A,你需要完成如下操作:
1 x y k:表示元素 Ax,y 自增k;
2 a b c d:表示询问左上角为(a,b),右下角为(c,d)的子矩阵内所有数的和。

here 按照之前的做法写就ok啦。

 1 #include<bits/stdc++.h>
 2 #define lowbit(x) x&(-x)
 3 #define int long long
 4 using namespace std;
 5 const int N=(1<<12)+5;
 6 int n,m,opt,a,b,c,d,k,f[N][N];
 7 void put(int x,int y,int z){ 
 8     for(int i=x;i<=n;i+=lowbit(i))
 9         for(int j=y;j<=m;j+=lowbit(j)) f[i][j]+=z;
10 }
11 int get(int x,int y){
12     int res=0;
13     for(int i=x;i;i-=lowbit(i))
14         for(int j=y;j;j-=lowbit(j)) res+=f[i][j];
15     return res;
16 }
17 signed main(){
18     //freopen(".in","r",stdin);
19     //freopen(".out","w",stdout);
20     scanf("%lld%lld",&n,&m);
21     while(~scanf("%lld",&opt)){
22         if(opt==1){
23             scanf("%lld%lld%lld",&a,&b,&k);
24             put(a,b,k);
25         }
26         else{
27             scanf("%lld%lld%lld%lld",&a,&b,&c,&d);
28             printf("%lld\n",get(c,d)-get(a-1,d)-get(c,b-1)+get(a-1,b-1));
29         }
30     }
31     return 0;
32 }

 

「二维树状数组2」区间修改,单点查询

题目链接 

给出一个n×m的零矩阵A,你需要完成如下操作:
1 a b c d k:表示左上角为(a,b),右下角为(c,d)的子矩阵内所有数都自增k;
2 x y:表示询问元素 Ax,y 的值。

here 二维树状数组区间修改和一维树状数组区间修改的原理是一样的,按照这个做法写一下就AC啦。

 1 #include<bits/stdc++.h>
 2 #define lowbit(x) x&(-x)
 3 #define int long long
 4 using namespace std;
 5 const int N=(1<<12)+5;
 6 int n,m,opt,a,b,c,d,k,f[N][N];
 7 void put(int x,int y,int z){ 
 8     for(int i=x;i<=n;i+=lowbit(i))
 9         for(int j=y;j<=m;j+=lowbit(j)) f[i][j]+=z;
10 }
11 int get(int x,int y){
12     int res=0;
13     for(int i=x;i;i-=lowbit(i))
14         for(int j=y;j;j-=lowbit(j)) res+=f[i][j];
15     return res;
16 }
17 signed main(){
18     //freopen(".in","r",stdin);
19     //freopen(".out","w",stdout);
20     scanf("%lld%lld",&n,&m);
21     while(~scanf("%lld",&opt)){
22         if(opt==1){
23             scanf("%lld%lld%lld%lld%lld",&a,&b,&c,&d,&k);
24             put(a,b,k),put(a,d+1,-k),put(c+1,b,-k),put(c+1,d+1,k);
25         }
26         else{
27             scanf("%lld%lld",&a,&b);
28             printf("%lld\n",get(a,b));
29         }
30     }
31     return 0;
32 }

  

「二维树状数组3」区间修改,区间查询

题目链接

给出一个n×m的零矩阵,直到输入文件结束,你需要进行若干个操作,操作有两类:
1 a b c d x:表示左上角为(a,b),右下角为(c,d)的子矩阵内全部加上x;
2 a b c d:表示询问左上角为(a,b),右下角为(c,d)为顶点的子矩阵的所有数字之和。

here 可参考这里

 1 #include<bits/stdc++.h>
 2 #define lowbit(x) x&(-x)
 3 #define int long long
 4 using namespace std;
 5 const int N=2100;
 6 int n,m,opt,a,b,c,d,k;
 7 struct data{
 8     int f[N][N];
 9     void put(int x,int y,int z){ 
10         for(int i=x;i<=n;i+=lowbit(i))
11             for(int j=y;j<=m;j+=lowbit(j)) f[i][j]+=z;
12     }
13     int get(int x,int y){
14         int res=0;
15         for(int i=x;i;i-=lowbit(i))
16             for(int j=y;j;j-=lowbit(j)) res+=f[i][j];
17         return res;
18     }
19 }v1,v2,v3,v4;
20 void put2(int x,int y,int k){
21     v1.put(x,y,k);
22     v2.put(x,y,(y-1)*k);
23     v3.put(x,y,(x-1)*k);
24     v4.put(x,y,(x-1)*(y-1)*k);
25 }
26 int get2(int x,int y){
27     return x*y*v1.get(x,y)-x*v2.get(x,y)-y*v3.get(x,y)+v4.get(x,y);
28 }
29 signed main(){
30     //freopen(".in","r",stdin);
31     //freopen(".out","w",stdout);
32     scanf("%lld%lld",&n,&m);
33     while(~scanf("%lld",&opt)){
34         if(opt==1){
35             scanf("%lld%lld%lld%lld%lld",&a,&b,&c,&d,&k);
36             put2(a,b,k),put2(a,d+1,-k),put2(c+1,b,-k),put2(c+1,d+1,k);
37         }
38         else{
39             scanf("%lld%lld%lld%lld",&a,&b,&c,&d);
40             printf("%lld\n",get2(c,d)-get2(a-1,d)-get2(c,b-1)+get2(a-1,b-1));
41         }
42     }
43     return 0;
44 }

 

「算法笔记」树状数组

原文:https://www.cnblogs.com/maoyiting/p/12561756.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!