题目
给定长度为N(N<=100000)的数列A,然后输入Q(Q<=100000)行操作指令
第一类指令形如“C l r d”,表示把数列中第l~r个数都加d
第二类指令形如“Q l r”,表示询问数列中第l~r个数的和
题解
可以用分块/线段树/树状数组
注意相加后答案可能超出32位整数
分块
分块就是通过适当的划分,预处理一部分信息并保存下来,方便后面的查询。不一定像这题涉及数量的加减,也可以有其他的
分块的效率往往比不上树状数组和线段树,但分块更好理解,更好实现
分块一般是把数组分成若干个长度不超过√n的段,然后对这些段的数据进行处理
在这题里,用
add[i]表示区间i所有数所加的量,初始值为0
a[i]表示每个数,整段修改时只修改add[i],不修改a[i]
sum[i]表示区间i的和,整段修改时不修改,a[i]的变化计入
对于修改或查询的区间l~r(例:2~9)
第2个数处于区间1,第9个数处于区间3
要整段修改或查询的是1+1~3-1区间,即区间2,就把区间2的add[i]修改,或把区间2的add[i]*区间2的长度计入答案
要朴素修改或查询的是2~区间1末尾&区间3开头~9,即2~3和7~9,就分别对每一个数的a[i]修改,计入sum[i],查询就把a[i]计入答案
1 2 3 | 4 5 6 | 7 8 9 | 10
区间1| 区间2 | 区间3 |区间4
分块,“大段维护,局部朴素”
线段树
很适合这题啊
树状数组
不会
代码
分块
#include <cstdio>
#include <cmath>
#include <string>
#include <cstring>
#include <algorithm>
using namespace std;
const int N=100005;
long long add[N],sum[N],a[N];
int n,m,t,p[N],L[N],R[N];
int main(){
scanf("%d%d",&n,&m);
for (int i=1;i<=n;i++) scanf("%lld",&a[i]);
t=sqrt(n);
for (int i=1;i<=t;i++){
L[i]=(i-1)*t+1;
R[i]=i*t;
}
if (R[t]<n) L[++t]=R[t-1]+1,R[t]=n;
for (int i=1;i<=t;i++)
for (int j=L[i];j<=R[i];j++){
p[j]=i;
sum[i]+=a[j];
}
for (int i=1;i<=m;i++){
char c[2];
int l,r,x,y,k;
long long ans=0;
scanf("%s%d%d",&c,&l,&r);
x=p[l];
y=p[r];
if (c[0]=='Q'){
if (x!=y){
for (int j=x+1;j<=y-1;j++)
ans+=add[j]*(R[j]-L[j]+1)+sum[j];
for (int j=l;j<=R[x];j++)
ans+=a[j];
ans+=add[x]*(R[x]-l+1);
for (int j=L[y];j<=r;j++)
ans+=a[j];
ans+=add[y]*(r-L[y]+1);
} else
{
for (int j=l;j<=r;j++)
ans+=a[j];
ans+=add[x]*(r-l+1);
}
printf("%lld\n",ans);
}else{
scanf("%d",&k);
if (x!=y){
for (int j=x+1;j<=y-1;j++)
add[j]+=k;
for (int j=l;j<=R[x];j++)
a[j]+=k;
for (int j=L[y];j<=r;j++)
a[j]+=k;
sum[x]+=(R[x]-l+1)*k;
sum[y]+=(r-L[y]+1)*k;
} else
{
for (int j=l;j<=r;j++)
a[j]+=k;
sum[x]+=(r-l+1)*k;
}
}
}
}