题目描述
给定一个长度为 N 的数列 A,以及 M 条指令,每条指令可能是以下两种之一:
C l r d,表示把 A[l],A[l+1],…,A[r] 都加上 d。
Q l r,表示询问数列中第 l∼r 个数的和。
对于每个询问,输出一个整数表示答案。
题解
在区间修改,单点查询的树状数组中,我们维护的差分数组的累加和
最终答案也就是在
a
x
a_x
ax的基础上增加了
∑
i
=
1
x
b
i
\sum_{i=1}^{x}b_i
∑i=1xbi,(
b
b
b数组为朴素的差分数组)
那么序列
a
a
a的前缀和经修改后的最终答案也就是在
a
1
+
a
2
+
…
+
a
x
a_1+a_2+…+a_x
a1+a2+…+ax的基础上增加了
∑
j
=
1
x
∑
i
=
1
j
b
i
\sum_{j=1}^{x}\sum_{i=1}^{j}b_i
∑j=1x∑i=1jbi
接下来最关键的一步,对此式子进行化简:
∑
j
=
1
x
∑
i
=
1
j
b
i
=
∑
j
=
1
x
(
x
−
j
+
1
)
∗
b
j
=
(
x
+
1
)
∑
j
=
1
x
b
j
−
∑
j
=
1
x
j
∗
b
j
\sum_{j=1}^{x}\sum_{i=1}^{j}b_i=\sum_{j=1}^{x}(x-j+1)*b_j=(x+1)\sum_{j=1}^xb_j-\sum_{j=1}^xj*b_j
∑j=1x∑i=1jbi=∑j=1x(x−j+1)∗bj=(x+1)∑j=1xbj−∑j=1xj∗bj
观察上述式子,我们对每一项逐个进行处理
首先原序列
a
1
+
…
+
a
x
a_1+…+a_x
a1+…+ax的累加和很容易用前缀和就能维护出来
(x+1)是定值,不用管
后面的两项前缀和,前一项很明显就是区间修改,单点查询中的那一个树状数组,而后一项我们用另一个树状数组来维护出来即可
具体而言,对于修改操作:
A树状数组
l
l
l加
d
d
d
A树状数组
r
+
1
r+1
r+1减
d
d
d
B树状数组
l
l
l加
l
∗
d
l*d
l∗d
B树状数组
r
+
1
r+1
r+1减
(
r
+
1
)
∗
d
(r+1)*d
(r+1)∗d
而查询操作套用公式即可
code
#include<bits/stdc++.h>
using namespace std;
int n,m;
const int N=1e5+10;
int a[N];
long long c1[N],c2[N],sum[N];
void change1(int x,int y)
{
for(;x<=n;x+=x&-x) c1[x]+=y;
}
void change2(int x,int y)
{
for(;x<=n;x+=x&-x) c2[x]+=y;
}
long long ask1(int x)
{
long long ans=0;
for(;x;x-=x&-x) ans+=c1[x];
return ans;
}
long long ask2(int x)
{
long long ans=0;
for(;x;x-=x&-x) ans+=c2[x];
return ans;
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
sum[i]=sum[i-1]+a[i];
}
for(int i=1;i<=m;i++)
{
char c[3];
scanf("%s",c);
if(c[0]=='C')
{
int l,r,d;
scanf("%d%d%d",&l,&r,&d);
change1(l,d);
change1(r+1,-d);
change2(l,l*d);
change2(r+1,-(r+1)*d);
}
else
{
int l,r;
scanf("%d%d",&l,&r);
printf("%lld\n",(sum[r]+1ll*(r+1)*ask1(r)-ask2(r))-(sum[l-1]+1ll*l*ask1(l-1)-ask2(l-1)));
}
}
return 0;
}