在上一篇博客中,讲解了一些关于树状数组基础的部分以及其最简单的用法:区间查询,单点修改,还没有看过的请戳这里
细心的童鞋可能已经发现树状数组的查询是前缀和的查询,于是可以利用这个形式扩展出很多其他的用法
比如说
单点查询,区间修改
其查询前缀和的性质,直接用树状数组维护一个差分数组,那么每次查询得到差分和前 n n 项,即
区间修改 [l,r] [ l , r ] 时只改变了 a[l−1],a[l] a [ l − 1 ] , a [ l ] 的差和 a[r],a[r+1] a [ r ] , a [ r + 1 ] 的差,修改 l,r+1 l , r + 1 位置即可
void insert(int x,int add) {
while(x<=n) {a[x]+=add;x+=lowbit(x);}
}
int query(int x) {
int ans=0;
while(x<=n) {ans+=a[x];x+=lowbit(x);}
return ans;
}
其修改查询操作与基本方法完全一致,但是维护的东西不同
for(int i=1;i<=n;++i) s[i]=read();
for(int i=1;i<=n;++i) insert(i,s[i]-s[i-1]);
修改时:(以区间加为例)
l=read();r=read();add=read();
insert(l,add);insert(r+1,-add);
区间查询,区间修改
大部分认为,树状数组仅仅能单点查询,区间修改
或者是区间查询,单点修改
,但是这样就显得很naive
.
其实很简单的,就能使树状数组资瓷区间查询,区间修改
显然,既然要区间修改,那么前缀和没跑了
另外,为了区间查询,我们来分析一下每次查询都能得到些什么
假设
c[i]=s[i]−s[i−1]
c
[
i
]
=
s
[
i
]
−
s
[
i
−
1
]
假设要查询
∑xi=1s[i]
∑
i
=
1
x
s
[
i
]
,需要的是
等价于
那么如果新维护一个
d[i]=i∗c[i]
d
[
i
]
=
i
∗
c
[
i
]
的数组,那么查询它能够得到
显然 x∗query(c,x)−query(d.x)=s[x]+s[x−1]+s[x−2]+⋯+s[1]=∑xi=1s[i] x ∗ q u e r y ( c , x ) − q u e r y ( d . x ) = s [ x ] + s [ x − 1 ] + s [ x − 2 ] + ⋯ + s [ 1 ] = ∑ i = 1 x s [ i ]
得解
同时为了优化把两个数组的修改查询放一起
void insert(int x,int add) {
int pos=x;
while(x<=n) {a[x]+=add;b[x]+=add*pos;x+=lowbit(x);}
}
int query(int x) {
int ans=0,pos=x;
while(x) {ans+=a[x]*pos;ans-=b[x];x+=lowbit(x);}
return ans;
}
二维树状数组
搞定序列,考虑一下在平面中的问题
由于树状数组的查询等同于查询前缀和,那么定义
query(x,y)
q
u
e
r
y
(
x
,
y
)
等同于查询
(1,1)−(x,y)
(
1
,
1
)
−
(
x
,
y
)
子矩阵的和
其实就相当于树状数组套树状数组了(雾
void insert(int x,int y,int add) {
if(x>n || y>n) return;
while(x<=n) {
int newy=y;
while(newy<=n) {a[x][newy]+=add;a[x][newy]%=2;newy+=lowbit(newy);}
x+=lowbit(x);
}
}
int query(int x,int y) {
int ans=0;
while(x) {
int newy=y;
while(newy) {ans+=a[x][newy];ans%=2;newy-=lowbit(newy);}
x-=lowbit(x);
}
return ans;
}
单点查询,区间修改
老办法,维护差分数组
由于每次查询的到的是一个矩阵的和,那么我们在
(x,y)
(
x
,
y
)
这个点应该放的是
观察发现每次修改
(x,y)
(
x
,
y
)
,影响的是区间
(x,y)−(n,m)
(
x
,
y
)
−
(
n
,
m
)
所以若需区间修改
(x1,y1)−(x2,y2)
(
x
1
,
y
1
)
−
(
x
2
,
y
2
)
,相当于修改
4
4
个位置
每个位置应该修改的值为
(add,−add,−add,add)
(
a
d
d
,
−
a
d
d
,
−
a
d
d
,
a
d
d
)
int x1=read(),y1=read(),x2=read(),y2=read(),add=read();
insert(x1,y1,add);insert(x2+1,y2+1,add);
insert(x1,y2+1,-add);insert(x2+1,y1,-add);
区间查询,区间修改
类比一维的情况下,考虑每次查询得到些什么
假设
c[i][j]=s[i][j]−s[i−1][j]−s[i][j−1]+s[i−1][j−1]
c
[
i
]
[
j
]
=
s
[
i
]
[
j
]
−
s
[
i
−
1
]
[
j
]
−
s
[
i
]
[
j
−
1
]
+
s
[
i
−
1
]
[
j
−
1
]
假设要查询
∑xi=1∑yj=1s[i][j]
∑
i
=
1
x
∑
j
=
1
y
s
[
i
]
[
j
]
,需要的是
等价于
所以说只需要维护四个二维树状数组 c[i][j],c[i][j]∗i,c[i][j]∗j,c[i][j]∗i∗j c [ i ] [ j ] , c [ i ] [ j ] ∗ i , c [ i ] [ j ] ∗ j , c [ i ] [ j ] ∗ i ∗ j 即可
void insert(int x,int y,int add) {
int newx=x;
while(newx<=n) {
int newy=y;
while(newy<=n) {
a[0][newx][newy]+=add;
a[1][newx][newy]+=add*x;
a[2][newx][newy]+=add*y;
a[3][newx][newy]+=add*x*y;
newy+=lowbit(newy);
}
newx+=lowbit(newx);
}
}
int query(int x,int y) {
int newx=x,ans=0;
while(newx) {
int newy=y;
while(newy) {
ans+=(x+1)*(y+1)*a[0][newx][newy];
ans-=(y+1)*a[1][newx][newy];
ans-=(x+1)*a[2][newx][newy];
ans+=a[3][newx][newy];
newy-=lowbit(newy);
}
newx-=lowbit(newx);
}
return ans;
}