洛谷 P4868 【Preprefix sum】
线段树题解qwq
这道题其实把式子一推就行了。因为要求前缀和的前缀和,那为什么我们不维护前缀和,然后求前缀和呢?对于维护前缀和,我们只需要两种操作:区间修改,区间查询。
区间查询是查询前缀和的前缀和嘛,这个是区间操作没问题,但为什么是区间修改?题目不是单点修改吗?我们先推一下式子:
原数列:
\(x_1,x_2,x_3,x_4,x_5\)
前缀和:
\(x_1,x_1+x_2,x_1+x_2+x_3,x_1+x_2+x_3+x_4,x_1+x_2+x_3+x_4+x_5\)
可以发现,我们如果修改了\(x_1\),对于后面的每一个数都变化了,继续推,发现每一个\(x\)的变化都会影响到后面的每一个数,所以当修改\(x_i\)时,我们要修改的范围其实是\(i-n\),那么线段树模板就来了。
诶等等,他是修改,而不是加减,这里坑就来了,我们要先拿一个数组存储一下每一次修改后的序列,这样对于下一次修改的时候,我们就可以直接比较一下修改数和原数,这样就可以把修改数转化为加减了。
#include <bits/stdc++.h>
using namespace std;
#define ls 2 * p
#define rs 2 * p + 1
long long n , m , last; //太懒了直接全部long long
long long sum[4000010] , tag[4000010] , a[1000010];
void pushdown(long long l , long long r , long long p){ //下传标记
if(l == r || tag[p] == 0) return;
long long mid = (l + r) / 2;
sum[ls] += (mid - l + 1) * tag[p];
sum[rs] += (r - mid) * tag[p];
tag[ls] += tag[p];
tag[rs] += tag[p];
tag[p] = 0;
}
void add(long long l , long long r , long long s , long long t , long long k , long long p){ //区间加
if(l >= s && r <= t){
sum[p] += (r - l + 1) * k;
tag[p] += k;
return;
}
pushdown(l , r , p);
long long mid = (l + r) / 2;
if(mid >= s) add(l , mid , s , t , k , ls);
if(mid < t) add(mid + 1 , r , s , t , k , rs);
sum[p] = sum[ls] + sum[rs];
}
long long query(long long l , long long r , long long s , long long t , long long p){ //区间求和
if(l >= s && r <= t) return sum[p];
pushdown(l , r , p);
long long mid = (l + r) / 2 , ans = 0;
if(mid >= s) ans += query(l , mid , s , t , ls);
if(mid < t) ans += query(mid + 1 , r , s , t , rs);
return ans;
}
int main(){
cin >> n >> m;
for(long long i = 1; i <= n; i++){
cin >> a[i];
add(1 , n , i , i , a[i] + last , 1); //维护前缀和
last += a[i];
}
while(m--){
string st;
long long x , k;
cin >> st;
if(st[0] == 'Q'){
cin >> x;
cout << query(1 , n , 1 , x , 1) << endl;
}else{
cin >> x >> k;
add(1 , n , x , n , k - a[x] , 1); //转化为区间加减
a[x] = k; //存一下修改后序列
}
}
return 0;
}