给定一个长度为 N 的数列 A,以及 M 条指令,每条指令可能是以下两种之一:
C l r d,表示把 A[l],A[l+1],…,A[r] 都加上 d。
Q l r,表示询问数列中第 l∼r 个数的和。
对于每个询问,输出一个整数表示答案。
输入格式
第一行两个整数 N,M。
第二行 N 个整数 A[i]。
接下来 M 行表示 M 条指令,每条指令的格式如题目描述所示。
输出格式
对于每个询问,输出一个整数表示答案。
每个答案占一行。
数据范围
1≤N,M≤105,
|d|≤10000,
|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
分析
- 此题和上题: 242. 一个简单的整数问题——差分思想+树状数组在查询操作上有点不同,区间修改同样和上题一样,采用tr维护差分数组的前缀和即可;那么区间查询怎么解决呢,可以通过下图的填充构造的方式推出一个式子:(x+1) * ∑(i:1~x) b[i] − ∑ (i:1~x) i×b[i];
- 所以需要维护两个树状数组,tr1维护差分数组b[i]的前缀和, tr2 维护b[i] * i的前缀和;然后关于sum、add的参数x问题,此题x为索引下标,因为求的前缀和和是以下标区间来找,而 楼兰图腾那个题,x是a[i];注意区分和理解含义;
- getSum函数是求a1+a2+…+ax的和,通过结论的式子来计算:区间[1,x]的 tr1的和 - 区间[1,x]的tr2的和;然后在下面通过两个tr作差,求任意区间的a[i]的和:[1,r] - [1,l-1] == [l,r];
- 前缀和问题看下数据范围,保险起见可以都加LL;
- 下图来自yxc,还有一篇优秀题解可以参考:AcWing 243. 一个简单的整数问题2 作者: 一只野生彩色铅笔;
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 100010;
int n, m;
int a[N];
LL tr1[N]; //维护差分数组b[i]的前缀和
LL tr2[N]; //维护b[i] * i的前缀和
int lowbit(int x) {
return x & -x;
}
//tr为指明对哪个前缀和数组做操作,x为索引位置
void add(LL tr[], int x, LL c) {
for (int i = x; i <= n; i += lowbit(i)) {
tr[i] += c;
}
}
//x:为索引下标,因为求的和是以下标区间来找,而楼兰图腾那个题,x是a[i]
LL sum(LL tr[], int x) {
LL res = 0;
for (int i = x; i; i -= lowbit(i)) {
res += tr[i];
}
return res;
}
//求a1+a2+...+an的和的结论的式子:区间[1,x]的 tr1的和 - tr2的和
LL getSum(int x) {
return sum(tr1, x) * (x + 1) - sum(tr2, x);
}
int main() {
cin >> n >> m;
for (int i = 1; i <= n; ++i) {
cin >> a[i];
}
for (int i = 1; i <= n; ++i) {
int b = a[i] - a[i - 1];//差分
add(tr1, i, b);
add(tr2, i, (LL) b * i);
}
while (m--) {
char op;
int l, r, d;
cin >> op >> l >> r;
if (op == 'Q') {
//[1,r] - [1,l-1] == [l,r]
cout << getSum(r) - getSum(l - 1) << endl;
} else {
cin >> d;
// b[l]+d
add(tr1, l, d);
add(tr2, l, l * d);
// b[r+1]-d
add(tr1, r + 1, -d);
add(tr2, r + 1, (r + 1) * -d);
}
}
return 0;
}