好东西,以后可以不打线段树了
本篇假定读者都会最基础的两种树状数组,即区改单查和单改区查
思考如何维护一个区间的值,想到了差分
对一个差分数组做一次前缀和可以得到每个位置的值
再对每个位置累加一下就是一个区间的值
公式化的讲,就是
设差分数组为
c
c
c
则每个位置的值
v
a
l
i
=
∑
j
=
1
i
c
j
val_i=\sum\limits_{j=1}^ic_j
vali=j=1∑icj
一个区间
[
l
,
r
]
[l,r]
[l,r]的值
s
l
,
r
=
∑
i
=
l
r
v
a
l
i
s_{l,r}=\sum\limits_{i=l}^rval_i
sl,r=i=l∑rvali
写成前缀和相减的形式就是
s
l
,
r
=
∑
i
=
1
r
v
a
l
i
−
∑
i
=
1
l
−
1
v
a
l
i
s_{l,r}=\sum\limits_{i=1}^rval_i-\sum\limits_{i=1}^{l-1}val_i
sl,r=i=1∑rvali−i=1∑l−1vali
不难发现,一个区间的值实际上就是差分数组前缀和的前缀和做减法
也就是说,我们只要维护出前缀和的前缀和就可以用树状数组维护区间了
考虑如何维护前缀和的前缀和
s
p
=
∑
i
=
1
p
∑
j
=
1
i
c
j
s_p=\sum\limits_{i=1}^p\sum\limits_{j=1}^ic_j
sp=i=1∑pj=1∑icj
考虑每个
c
j
c_j
cj的出现次数,可以得到
s
p
=
∑
i
=
1
p
(
p
−
i
+
1
)
c
i
=
(
p
+
1
)
∑
i
=
1
p
c
i
−
∑
i
=
1
p
i
∗
c
i
s_p=\sum\limits_{i=1}^p\left(p-i+1\right)c_i=\left(p+1\right)\sum\limits_{i=1}^pc_i-\sum\limits_{i=1}^pi *c_i
sp=i=1∑p(p−i+1)ci=(p+1)i=1∑pci−i=1∑pi∗ci
经过如上的简单推导,我们只要维护
∑
i
=
1
p
c
i
\sum\limits_{i=1}^pc_i
i=1∑pci和
∑
i
=
1
p
i
∗
c
i
\sum\limits_{i=1}^pi *c_i
i=1∑pi∗ci这两个东西就可以了
前者就是差分数组,而后者我们只要在维护差分数组时乘以相应的位置的下标即可
这两个东西我们都可以用树状数组维护单点修改区间查询一样的方法维护
int c1[maxn],c2[maxn];
int lbt (int x){ return x & -x; }
void modify (int l,int r,int v)//维护差分数组
{
++r;
for (int i=l;i<=n;i+=lbt(i)) c1[i]+=v,c2[i]+=l*v;
for (int i=r;i<=n;i+=lbt(i)) c1[i]-=v,c2[i]-=r*v;
}
最开始这两句我没有看懂,不是说好的维护
i
∗
c
i
i*c_i
i∗ci吗,怎么写出来时就变成
l
∗
v
l*v
l∗v和
r
∗
v
r*v
r∗v了
如果你也有这样的疑惑,这说明太久没打树状数组或者没想树状数组的原理你也忘记树状数组的原理了
这个东西维护的是差分数组,而不是差分数组的前缀和!
对于查询
int query (int l,int r)
{
int res=0;
for (int i=r;i;i-=lbt(i)) res+=(r+1)*c1[i]-c2[i];
for (int i=l-1;i;i-=lbt(i)) res-=l*c1[i]-c2[i];
return res;
}
如有哪里讲得不是很明白或是有错误,欢迎指正
如您喜欢的话不妨点个赞收藏一下吧