【模板】树状数组(7.26)
update:二维树状数组的实现
一.概述
树状数组,是一个区间查询和单点修改复杂度都为log(n)的数据结构。主要用于查询任意两点之间的所有元素之和。
树状数组的题多半要转化,利用差分数组,可以将区间修改变为单点修改,单点查询变为区间查询(即前缀和)。只要满足单点修改,我们总能用树状数组来维护前缀和。
b i t [ i ] bit[i] bit[i]数组可以看作一个树形结构,它满足以下三个性质:
- 每个内部节点 b i t [ x ] bit[x] bit[x]保存以它为根的子树中所有叶节点的和
- 每个内部节点 b i t [ x ] bit[x] bit[x]的子节点个数等于 l o w b i t ( x ) lowbit(x) lowbit(x)的位数
- 除树根外,每个内部节点bit[x]的父节点是bit[x+lowbit(x)]
- 树的深度为 O ( l o g N ) O(logN) O(logN)
- b i t [ x ] bit[x] bit[x]表示区间 [ x − l o w b i t ( x ) + 1 , x ] [x-lowbit(x)+1,x] [x−lowbit(x)+1,x]
个人认为它比较数学化,利用了数的性质(尤其是二进制)。
二.例题
- 模拟类
- 计数类(偏数学)
一维树状数组:
1.单点修改,区间查询
2.区间修改,区间查询
引入差分概念:
c [ i ] = a [ i ] − a [ i − 1 ] c[i]=a[i]-a[i-1] c[i]=a[i]−a[i−1]
那么某个元素的值其实等于
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=1x∑j=1ic[i]=∑i=1xc[i]∗(x−i+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)
(i−lowbit(i)+1,j−lowbit(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) O(logn∗logn∗t),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[i−1][j]−a[i][j−1]+a[i−1][j−1]。
然后神奇的发现:
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=1i∑y=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=1x∑j=1ya[i][j]=∑i=1x∑j=1yd[i][j]∗(x−i+1)∗(y−j+1)=∑i=1x∑j=1yd[i][j]∗(xy+x+y+1)−d[i][j]∗i∗(y+1)−d[i][j]∗j∗(x+1)+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]*i*j d[i][j],d[i][j]∗i,d[i][j]∗j,d[i][j]∗i∗j
这样就可以解决上述问题了
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));
}

浙公网安备 33010602011771号