题面
分析
如果用线段树做那就是裸题了,但是用树状数组我们需要转化一下;
首先看到区间加,我们考虑使用差分
;
对于差分数组来说,维护区间加是很容易的,只需要单点修改;
那么我们看看区间和有什么规律;
比如求区间 [ L , R ] [L,R] [L,R]的和,则是 a L + . . . + a R a_L+...+a_R aL+...+aR
那么对于差分数组b来说,则说 ∑ i = L R ∑ j = 1 i \sum_{i=L}^{R} \sum_{j=1}^{i} ∑i=LR∑j=1i;
这个是暴力求法,会TLE的;
我们将这个式子画出来,见下图,黑色表示真实要加的,红色表示填充上去多余的;
如果求区间
[
L
,
R
]
[L,R]
[L,R]直接代入上面那个式子的话,不太好求;
但是我们可以求出 [ 1 , R ] [1,R] [1,R]和 [ 1 , L − 1 ] [1,L-1] [1,L−1],相减即可,也就是前缀和的方式;
如果将上面式子的 L = 1 , R = x L=1,R=x L=1,R=x
可以得到式子 ( x + 1 ) ∗ ∑ i = 1 x b i − ∑ i = 1 x i ∗ b i (x+1)*\sum_{i=1}^x b_i - \sum_{i=1}^x i*b_i (x+1)∗∑i=1xbi−∑i=1xi∗bi
那么我们可以维护两个树状数组,其中 t r 1 维 护 差 分 数 组 b i , t r 2 维 护 i ∗ b i tr1维护差分数组b_i,tr2维护i*b_i tr1维护差分数组bi,tr2维护i∗bi即可;
至于单点修改就很好理解了,对于tr1来说直接操作即可;
对于tr2来说, 将 ( b i = b i + v ) 代 入 ( i ∗ b i ) 将(b_i = b_i+v)代入(i*b_i) 将(bi=bi+v)代入(i∗bi)可得 i ∗ b i + i ∗ v i*b_i+i*v i∗bi+i∗v;
因此只需要加上 i ∗ v i*v i∗v即可
Code
#include <iostream>
using namespace std;
const int N = 1e5+10;
typedef long long ll;
int n,m,a[N],b[N];
ll tr1[N],tr2[N];
int lowbit(int x){
return x & -x;
}
ll query(ll tr[],int x){
ll ret = 0;
for(int i=x;i;i-=lowbit(i)){
ret += tr[i];
}
return ret;
}
void add(ll tr[],int u,ll v){
for(int x=u;x<=n;x+=lowbit(x)){
tr[x] += v;
}
}
ll prefix_sum(int x){
return (x+1) * query(tr1,x) - query(tr2,x);
}
int main(){
cin >> n >> m;
for(int i=1;i<=n;++i){
cin >> a[i];
b[i] = a[i] - a[i-1];
add(tr1,i,b[i]);
add(tr2,i,1ll*i*b[i]);
}
char ch;
int l,r,d;
while(m--){
cin >> ch;
if(ch == 'Q'){
cin >> l >> r;
cout << prefix_sum(r) - prefix_sum(l-1) << '\n';
}else{
cin >> l >> r >> d;
add(tr1,l,d);add(tr1,r+1,-d);
add(tr2,l,l*d);add(tr2,r+1,(r+1)*-d);
}
}
return 0;
}