洛谷 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;
}
posted @ 2020-08-07 10:39  那一条变阻器  阅读(111)  评论(0编辑  收藏  举报