题目链接
题意:给定一个长度为N的数列A,以及M条指令,每条指令可能是以下两种之一:
1、“C l r d”,表示把 A[l],A[l+1],…,A[r] 都加上 d。
2、“Q l r”,表示询问 数列中第 l~r 个数的和。
对于每个询问,输出一个整数表示答案。
输入格式
第一行两个整数N,M。
第二行N个整数A[i]。
接下来M行表示M条指令,每条指令的格式如题目描述所示。
输出格式
对于每个询问,输出一个整数表示答案。
每个答案占一行。
数据范围
1≤N,M≤105,
|d|≤10000,
|A[i]|≤1000000000
输入样例:
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
思路1 (线段树):
用线段树来进行区间修改的话就需要用到懒标记法来实现啦。
代码实现:
#include
#include
#include
using namespace std;
typedef long long ll;
const int N = 100010;
int n, m;
int w[N];
struct node
{
int l, r;
//sum:如果只考虑当前节点和子节点上的所以标记,当前区间和是多少
//给当前节点的所有儿子加上add
ll sum, add;
}tr[N << 2];
void pushup(int u)
{
tr[u].sum = tr[u << 1].sum + tr[u << 1 | 1].sum;
}
//用两个子节点信息更新父节点信息
void pushdown(int u)
{
auto &root = tr[u], &left = tr[u << 1], &right = tr[u << 1 | 1];
//如果当前节点已经被标记
if(root. add){
left.add += root.add;
left.sum += (ll)(left.r - left.l + 1) * root.add;
right.add += root.add;
right.sum += (ll)(right.r - right.l + 1) * root.add;
root.add = 0;
}
}
void build(int u, int l, int r)
{
if(l == r) tr[u] = {l, r, w[r], 0};
else{
tr[u] = {l, r};
int mid = l + r >> 1;
build(u << 1, l, mid);
build(u << 1 | 1, mid + 1, r);
pushup(u);
}
}
//修改,将[l,r]的区间加上d
void modify(int u, int l, int r, int d)
{
if(tr[u].l >= l && tr[u].r <= r){
tr[u].sum += (ll)(tr[u].r - tr[u].l + 1) * d;
tr[u].add += d;
}
else{
//当前区间太大,需要分裂
pushdown(u);
int mid = tr[u].l + tr[u].r >> 1;
if(l <= mid) modify(u << 1, l, r, d);
if(r > mid) modify(u << 1 | 1, l, r, d);
pushup(u);
}
}
ll query(int u, int l, int r)
{
if(tr[u].l >= l && tr[u].r <= r)
return tr[u].sum;
pushdown(u);
ll sum = 0;
int mid = tr[u].l + tr[u].r >> 1;
if(l <= mid) sum = query(u << 1, l, r);
if(r > mid) sum += query(u << 1 | 1, l, r);
return sum;
}
int main()
{
scanf("%d%d", &n, &m);
for(int i = 1; i <= n; i ++)
scanf("%d", &w[i]);
build(1, 1, n);
char op[2];
int l, r, d;
while(m -- ){
scanf("%s%d%d", op, &l, &r);
if(*op == 'C'){
scanf("%d", &d);
modify(1, l, r, d);
}
else printf("%lld\n", query(1, l, r));
}
return 0;
}
思路2 (树状数组):
区间的修改可以用差分的思维来做,让b[l]+=d,b[r+1]-=d; 而对于求前缀和,我们可以推导出一个公式(用两个前缀和来维护):
就可以把原来的数组,经过差分操作去维护两个树状数组,一个维护di,一个维护 di×i 这样的话,我们在区间修改的过程中,就可以在两个树状数组中去查询得到前缀和.
代码实现:
#include
#include
#include
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;
}
void add(ll tr[], int x, ll c)
{
for(int i = x; i <= n; i += lowbit(i))
tr[i] += c;
}
ll sum(ll tr[], int x)
{
ll res = 0;
for(int i = x; i ; i -= lowbit(i))
res += tr[i];
return res;
}
ll prefix_sum(int x)
{
return sum(tr1, x) * (x + 1) - sum(tr2, x);
}
int main()
{
scanf("%d%d", &n, &m);
for(int i = 1; i <= n; i ++)
scanf("%d", &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[2];
int l, r, d;
scanf("%s%d%d", op, &l, &r);
if(*op == 'Q'){
printf("%lld\n",prefix_sum(r) - prefix_sum(l - 1));
}
else{
scanf("%d", &d);
//a[l] += d
add(tr1, l, d);
add(tr2, l, l * d);
//a[r + 1] -= d
add(tr1, r + 1, -d);
add(tr2, r + 1, (r + 1) * -d);
}
}
return 0;
}