【模板】树状数组(7.26)

update:二维树状数组的实现

一.概述

树状数组,是一个区间查询单点修改复杂度都为log(n)的数据结构。主要用于查询任意两点之间的所有元素之和。

树状数组的题多半要转化,利用差分数组,可以将区间修改变为单点修改单点查询变为区间查询(即前缀和)。只要满足单点修改,我们总能用树状数组来维护前缀和。

b i t [ i ] bit[i] bit[i]数组可以看作一个树形结构,它满足以下三个性质:

  1. 每个内部节点 b i t [ x ] bit[x] bit[x]保存以它为根的子树中所有叶节点的和
  2. 每个内部节点 b i t [ x ] bit[x] bit[x]的子节点个数等于 l o w b i t ( x ) lowbit(x) lowbit(x)的位数
  3. 除树根外,每个内部节点bit[x]的父节点是bit[x+lowbit(x)]
  4. 树的深度为 O ( l o g N ) O(logN) OlogN
  5. b i t [ x ] bit[x] bit[x]表示区间 [ x − l o w b i t ( x ) + 1 , x ] [x-lowbit(x)+1,x] [xlowbit(x)+1,x]

个人认为它比较数学化,利用了数的性质(尤其是二进制)。

二.例题

  1. 模拟类
  2. 计数类(偏数学)

一维树状数组:

1.单点修改,区间查询

2.区间修改,区间查询
引入差分概念:

c [ i ] = a [ i ] − a [ i − 1 ] c[i]=a[i]-a[i-1] c[i]=a[i]a[i1]

那么某个元素的值其实等于 a [ i ] = ∑ i = 1 n c i a[i]=\sum_{i=1}^{n}c_{i} a[i]=i=1nci
那么区间和呢?

我们可以知道

s u m [ x ] = ∑ i = 1 x ∑ j = 1 i c [ i ] = ∑ i = 1 x c [ i ] ∗ ( x − i + 1 ) = ∑ i = 1 x c [ i ] ∗ ( x + 1 ) − c [ i ] ∗ i sum[x]=\sum_{i=1}^{x}\sum_{j=1}^{i}c[i]=\sum_{i=1}^{x}c[i]*(x-i+1)=\sum_{i=1}^{x}c[i]*(x+1)-c[i]*i sum[x]=i=1xj=1ic[i]=i=1xc[i](xi+1)=i=1xc[i](x+1)c[i]i

因此我们需要维护两个值:差分数组 c [ i ] c[i] c[i] b [ i ] = c [ i ] ∗ i b[i]=c[i]*i b[i]=c[i]i

#include<bits/stdc++.h> #define int long long using namespace std; const int N=1e6+5; inline int read() { int X=0; bool flag=1; char ch=getchar(); while(ch<'0'||ch>'9') {if(ch=='-') flag=0; ch=getchar();} while(ch>='0'&&ch<='9') {X=(X<<1)+(X<<3)+ch-'0'; ch=getchar();} if(flag) return X; return ~(X-1); } int n,Q,bit[N],bit2[N],a[N]; void update(int x,int k) { for(int i=x;i<=n;i+=i&-i) bit[i]+=k,bit2[i]+=x*k; } int get_sum(int x) { int tot=0,tot2=0; for(int i=x;i;i-=i&-i) tot+=bit[i],tot2+=bit2[i]; return tot*(x+1)-tot2; } signed main() { n=read(),Q=read(); for(int i=1;i<=n;i++) { a[i]=read(); update(i,a[i]-a[i-1]); } while(Q--) { int type=read(),l=read(),r=read(); if(type==1) { int x=read(); update(l,x); update(r+1,-x); } else printf("%lld\n",get_sum(r)-get_sum(l-1)); } }

二维树状数组:

https://blog.csdn.net/qq_35885746/article/details/89247993

1. 单点修改,区间查询
思路: b i t [ i ] [ j ] bit[i][j] bit[i][j]表示 ( i − l o w b i t ( i ) + 1 , j − l o w b i t ( j ) + 1 ) (i-lowbit(i)+1,j-lowbit(j)+1) (ilowbit(i)+1,jlowbit(j)+1) ( i , j ) (i,j) (i,j)的子矩阵和,sum函数显然很好写:

ll Sum(int x, int y) { ll tot = 0; for (int i = x; i; i -= i & -i) { for (int j = y; j; j -= j & -j) { tot += bit[i][j]; } } return tot; }

关键的是update函数。仔细思考: i i i, j j j的父节点分别是 i + l o w b i t ( i ) − 1 i+lowbit(i)-1 i+lowbit(i)1 j + l o w b i t ( j ) − 1 j+lowbit(j)-1 j+lowbit(j)1,我们要更新的 x x x, y y y必然都是 i i i, j j j的祖先节点,所以用二重循环遍历祖先节点即可。

void update(int x, int y, int k) { for (int i = x; i <= n; i += i & -i) { for (int j = y; j <= m; j += j & -j) { bit[i][j] += k; } } }

查询操作是 O ( 1 ) O(1) O(1):

long long get() { printf("%lld\n", Sum(c,d) - Sum(c,b-1) - Sum(a-1,d) + Sum(a-1,b-1)); }

总时间复杂度是 O ( l o g n ∗ l o g n ∗ t ) O(logn*logn*t) Olognlognt,t是操作总数

2.区间修改,单点查询
思路:这里维护的数组值是差分数组的前缀和。
我们可以令差分数组 d [ i ] [ j ] = a [ i ] [ j ] − a [ i − 1 ] [ j ] − a [ i ] [ j − 1 ] + a [ i − 1 ] [ j − 1 ] d[i][j]=a[i][j]- a[i-1][j]-a[i][j-1]+a[i-1][j-1] d[i][j]=a[i][j]a[i1][j]a[i][j1]+a[i1][j1]
然后神奇的发现: a [ i ] [ j ] = ∑ x = 1 i ∑ y = 1 j d [ x ] [ y ] a[i][j]=\sum_{x=1}^{i}\sum_{y=1}^{j}d[x][y] a[i][j]=x=1iy=1jd[x][y]
至于证明,显然成立(虽然这个式子比较难想)

long long Sum(int x, int y) { long long tot = 0; for (int i = x; i; i -= i & -i) { for (int j = y; j; j -= j & -j) { tot += bit[i][j]; } } return tot; } long long get() { printf("%lld\n", Sum(x,y)); }

然后考虑 d [ i ] [ j ] d[i][j] d[i][j] 数组的变化,发现一次区间修改相当于把四个点的d值进行修改。update函数把sum累加,正好就是点的变化。

void update(int x, int y, int k) { for (int i = x; i <= n; i += i & -i) { for (int j = y; j <= m; j += j & -j) { bit[i][j] += k; } } }

3.区间修改,区间查询
思路:仍然维护d数组,修改函数同上。
下面讨论如何计算区间的和:
s u m [ x ] [ y ] = ∑ i = 1 x ∑ j = 1 y a [ i ] [ j ] = ∑ i = 1 x ∑ j = 1 y d [ i ] [ j ] ∗ ( x − i + 1 ) ∗ ( y − j + 1 ) = ∑ i = 1 x ∑ j = 1 y d [ i ] [ j ] ∗ ( x y + x + y + 1 ) − d [ i ] [ j ] ∗ i ∗ ( y + 1 ) − d [ i ] [ j ] ∗ j ∗ ( x + 1 ) + d [ i ] [ j ] ∗ i ∗ j sum[x][y]=\sum_{i=1}^{x}\sum_{j=1}^{y}a[i][j]=\sum_{i=1}^{x}\sum_{j=1}^{y}d[i][j]*(x-i+1)*(y-j+1)=\sum_{i=1}^{x}\sum_{j=1}^{y}d[i][j]*(xy+x+y+1)-d[i][j]*i*(y+1)-d[i][j]*j*(x+1)+d[i][j]*i*j sum[x][y]=i=1xj=1ya[i][j]=i=1xj=1yd[i][j](xi+1)(yj+1)=i=1xj=1yd[i][j](xy+x+y+1)d[i][j]i(y+1)d[i][j]j(x+1)+d[i][j]ij
那么我们要开四个树状数组,分别维护:

d [ i ] [ j ] , d [ i ] [ j ] ∗ i , d [ i ] [ j ] ∗ j , d [ i ] [ j ] ∗ i ∗ j d[i][j],d[i][j]*i,d[i][j]*j,d[i][j]*i*j d[i][j],d[i][j]i,d[i][j]j,d[i][j]ij

这样就可以解决上述问题了

void update(int x, int y, long long k) { for (int i = x; i <= n; i += i & -i) { for (int j = y; j <= m; j += j & -j) { bit1[i][j] += k, bit2[i][j] += 1LL * k * x, bit3[i][j] += 1LL * k * y, bit4[i][j] += 1LL * x * y * k; } } } long long Sum(int x, int y) { long long tot = 0; for (int i = x; i; i -= i & -i) { for (int j = y; j; j -= j & -j) { tot += bit4[i][j] - (x + 1) * bit3[i][j] - (y + 1) * bit2[i][j] + (x + 1) * (y + 1) * bit1[i][j]; } } return tot; } long long get() { printf("%lld\n", Sum(c, d) - Sum(c, b - 1) - Sum(a - 1, d) + Sum(a - 1, b - 1)); }

__EOF__

本文作者仰望星空的蚂蚁
本文链接https://www.cnblogs.com/cqbzly/p/17530414.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   仰望星空的蚂蚁  阅读(4)  评论(0编辑  收藏  举报  
编辑推荐:
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 没有源码,如何修改代码逻辑?
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· DeepSeek 开源周回顾「GitHub 热点速览」
点击右上角即可分享
微信分享提示