题目链接
题目描述
给定一个长度为 N N N 的数列 A A A,以及 M M M 条指令,每条指令可能是以下两种之一:
C l r d
,表示把 A [ l ] , A [ l + 1 ] , … , A [ r ] A[l],A[l+1],…,A[r] A[l],A[l+1],…,A[r] 都加上 d d d。Q l r
,表示询问数列中第 l ∼ r l∼r l∼r 个数的和。
对于每个询问,输出一个整数表示答案。
输入格式
第一行两个整数 N , M N,M N,M。
第二行 N N N 个整数 A [ i ] A[i] A[i]。
接下来 M M M 行表示 M M M 条指令,每条指令的格式如题目描述所示。
输出格式
对于每个询问,输出一个整数表示答案。
每个答案占一行。
数据范围
- 1 ≤ N , M ≤ 1 0 5 1≤N,M≤10^5 1≤N,M≤105
- ∣ d ∣ ≤ 10000 |d|≤10000 ∣d∣≤10000
- ∣ A [ i ] ∣ ≤ 1 0 9 |A[i]|≤10^9 ∣A[i]∣≤109
输入样例:
10 5
1 2 3 4 5 6 7 8 9 10
Q 4 4
Q 1 10
Q 2 4
C 3 6 3
Q 2 4
输出样例:
4
55
9
15
解法:树状数组
这道题的数组
b
b
b 是原数组
a
a
a 的差分数组。
即:
- a [ 1 ] a[1] a[1] 的增量为 b [ 1 ] b[1] b[1]
- a [ 2 ] a[2] a[2] 的增量为 b [ 1 ] + b [ 2 ] b[1] + b[2] b[1]+b[2]
- a [ 3 ] a[3] a[3] 的增量为 b [ 1 ] + b [ 2 ] + b [ 3 ] b[1] + b[2] + b[3] b[1]+b[2]+b[3]
- a [ 4 ] a[4] a[4] 的增量为 b [ 1 ] + b [ 2 ] + b [ 3 ] + b [ 4 ] b[1] + b[2] + b[3] + b[4] b[1]+b[2]+b[3]+b[4]
- …
- a [ x ] a[x] a[x] 的增量为 b [ 1 ] + b [ 2 ] + b [ 3 ] + b [ 4 ] + . . . + b [ x ] b[1] + b[2] + b[3] + b[4] + ...+b[x] b[1]+b[2]+b[3]+b[4]+...+b[x]
那么对于数组 a a a 的前缀和 a [ 1 ∼ x ] a[1 \sim x] a[1∼x] 整体的增量就是:
b [ 1 ] + ( b [ 1 ] + b [ 2 ] ) + ( b [ 1 ] + b [ 2 ] + b [ 3 ] ) + ( b [ 1 ] + b [ 2 ] + b [ 3 ] + b [ 4 ] ) + . . . + ( b [ 1 ] + b [ 2 ] + b [ 3 ] + b [ 4 ] . . . + b [ x ] ) b[1] + (b[1] + b[2]) + (b[1] + b[2] + b[3]) + (b[1] + b[2] + b[3] + b[4]) + ... + (b[1] + b[2] + b[3] + b[4]...+b[x]) b[1]+(b[1]+b[2])+(b[1]+b[2]+b[3])+(b[1]+b[2]+b[3]+b[4])+...+(b[1]+b[2]+b[3]+b[4]...+b[x])
即 ∑ i = 1 x ∑ j = 1 i b [ j ] \sum_{i = 1}^{x}\sum_{j=1}^{i} b[j] ∑i=1x∑j=1ib[j]。
上式我们可以稍微将其转换一下形式:
∑ i = 1 x ∑ j = 1 i b [ j ] = ∑ i = 1 x ( x − i + 1 ) × b [ i ] = ( x + 1 ) ∑ i = 1 x b [ i ] − ∑ i = 1 x i × b [ i ] \sum_{i = 1}^{x}\sum_{j=1}^{i} b[j] = \sum_{i = 1}^{x}(x - i + 1) \times b[i] = (x + 1)\sum_{i = 1}^{x}b[i] - \sum_{i = 1}^{x}i \times b[i] i=1∑xj=1∑ib[j]=i=1∑x(x−i+1)×b[i]=(x+1)i=1∑xb[i]−i=1∑xi×b[i]
我们使用两个树状数组 c 0 c_0 c0 和 c 1 c_1 c1,开始时将它们初始化为 0 0 0。我们用 c 0 c_0 c0 来维护 b [ i ] b[i] b[i] 的前缀和 ;用 c 1 c_1 c1 来维护 i × b [ i ] i \times b[i] i×b[i] 的前缀和。
对于求区间和的操作 C l r d
:
- 在 c 0 c_0 c0 中,把位置 l l l 上的数加上 d d d
- 在 c 0 c_0 c0 中,把位置 r + 1 r + 1 r+1 上的数加上 − d -d −d
- 在 c 1 c_1 c1 中,把位置 l l l 上的数加上 l × d l \times d l×d
- 在 c 1 c_1 c1 中,把位置 r + 1 r + 1 r+1 上的数加上 − ( r + 1 ) × d -(r + 1) \times d −(r+1)×d
另外我们建立一个 a a a 的前缀和数组 s u m sum sum。
那么对于每一条查询指令 Q l r
:
我们最终的答案为:
- 区间 [ 1 , r ] [1,r] [1,r] 的和为 s 1 = s u m [ r ] + ( r + 1 ) × q u e r y ( c 0 , r ) − q u e r y ( c 1 , r ) s1 = sum[r] + (r + 1) \times query(c_0,r) - query(c_1,r) s1=sum[r]+(r+1)×query(c0,r)−query(c1,r)
- 区间 [ 1 , l − 1 ] [1,l - 1] [1,l−1] 的和为 s 2 = s u m [ l − 1 ] + l × q u e r y ( c 0 , l − 1 ) − q u e r y ( c 1 , l − 1 ) s2 = sum[l - 1] + l \times query(c_0,l - 1) - query(c_1,l-1) s2=sum[l−1]+l×query(c0,l−1)−query(c1,l−1)
- 那么最终的答案,区间 [ l , r ] [l,r] [l,r] 的和为 a n s = s 1 − s 2 ans = s1 - s2 ans=s1−s2
时间复杂度: O ( n × l o g n ) O(n \times logn) O(n×logn)
C++代码:
#include<iostream>
using namespace std;
const int N = 1e5+10;
using LL = long long;
//c[0] 表示原始数组 a 的差分数组 b[i] 的树状数组
//c[1] 表示原始数组的差分数组 b[i] * i 的树状数组
int a[N];
LL sum[N], c[2][N];
int n,m;
int lowbit(int x){
return x & (-x);
}
LL query(int t,int x){
LL ans = 0;
for(;x;x -= lowbit(x)) ans += c[t][x];
return ans;
}
void add(int t,int x,LL y){
for(;x <= n;x += lowbit(x)) c[t][x] += y;
}
int main(){
cin>>n>>m;
for(int i = 1;i <= n;i++){
scanf("%d" , &a[i]);
sum[i] = sum[i - 1] + a[i];
}
while(m--){
char op;
cin>>op;
int l ,r;
scanf("%d%d",&l,&r);
if(op == 'C'){
int d;
scanf("%d",&d);
add(0 , l , d);
add(0 , r + 1 , -d);
add(1 , l , l * 1LL * d);
add(1 , r + 1 , -(r + 1) * 1LL * d);
}
else{
LL ans = sum[r] + (r + 1) * 1LL * query(0 , r) - query(1 , r);
ans -= sum[l - 1] + l * 1LL * query(0 , l - 1) - query(1 , l - 1);
cout<<ans<<'\n';
}
}
}