1082 线段树练习 3 && 树状数组区间修改区间查询

1082 线段树练习 3

题意: 给定序列初值, 要求支持区间修改, 区间查询

Solution

用树状数组, 代码量小, 空间占用小
巧用增量数组, 修改时在 \(l\) 处 $ + val$ , \(r + 1\) 处 $ - val$, 在 \(x\) 处的值就是 \(\sum_{i = 1}^{x}c[i]\)
这就是区间更新, 单点求值的树状数组

那么怎么区间更新区间查询呢?
设增量数组为 \(b[i]\)
显然, 查询 \(1 - x\) 的答案为: \(\sum_{i = 1}^{x}\sum_{j = 1}^{i}b[i]\)
这样还不明朗, 无法得到一个比较通项的前缀和, 所以进行如下变换:

\[\sum_{i = 1}^{x}\sum_{j = 1}^{i}b[i]$$$$=\sum_{i = 1}^{x}(x - i + 1) * b[i]$$$$=(x + 1)\sum_{i = 1}^{x}b[i] - \sum_{i = 1}^{x}i * b[i] \]

这样一来, 我们通过两个树状数组分别维护 \(\sum_{i = 1}^{x}b[i]\)\(\sum_{i = 1}^{x}i * b[i]\), 即可快速计算得答案

具体的, 第一个树状数组 我们在 \(l\) 处 $ + val$ , \(r + 1\)\(- val\), 第二个树状数组在 \(l\) 处 $ + l * val$, \(r + 1\)\(- (r + 1) * val\)
\(1-x\)前缀和即为 \((x + 1) * get\_sum(x, 0) - get\_sum(x, 1)\)

Code

#include<iostream>
#include<cstdio>
#include<queue>
#include<cstring>
#include<algorithm>
#include<climits>
#define LL long long
using namespace std;
LL RD(){
    LL out = 0,flag = 1;char c = getchar();
    while(c < '0' || c >'9'){if(c == '-')flag = -1;c = getchar();}
    while(c >= '0' && c <= '9'){out = out * 10 + c - '0';c = getchar();}
    return flag * out;
    }
const LL maxn = 400019;
LL num, na;
#define lowbit(i) ((i) & (-i))
LL v[maxn], sum[maxn];
LL c[maxn][2];
void update(LL x, LL val, LL o){
	for(LL i = x;i <= num;i += lowbit(i))c[i][o] += val;
	}
LL get_sum(LL x, LL o){
	LL ans = 0;
	for(LL i = x;i > 0;i -= lowbit(i))ans += c[i][o];
	return ans;
	}
int main(){
	num = RD();
	for(LL i = 1;i <= num;i++)v[i] = RD(), sum[i] = sum[i - 1] + v[i];
	na = RD();
	for(LL i = 1;i <= na;i++){
		LL cmd = RD(), l = RD(), r = RD();
		if(cmd == 1){
			LL val = RD();
			update(l, val, 0), update(r + 1, -val, 0);
			update(l, l * val, 1), update(r + 1, (r + 1) * -val, 1);
			}
		else{
			printf("%lld\n",sum[r] - sum[l - 1] + (r + 1) * get_sum(r, 0) - get_sum(r, 1) - l * get_sum(l - 1, 0) + get_sum(l - 1, 1));
			}
		}
	}
posted @ 2018-09-03 12:16  Tony_Double_Sky  阅读(127)  评论(0编辑  收藏  举报