原题大概意思是,维护一个长为n的数组A[i],m个操作,每次往一个区间【l,r】加入一个首项为x,公差为d的等差数列,或者求区间【l,r】的和。
按理说用线段树也能做,不过貌似有点麻烦。所以用的桶分割和树状数组。
桶分割: 把数组分成sqrt(n)个桶,每个桶容量为S,每个桶储存一个等差数列,初始化为0,维护3个值: 桶内元素的和B,桶内第一个元素的值Bh,桶内序列的公差Bd。
每次加入时,若添加区间完全包括某一个桶j,由于桶初始为0,可看做一个等差数列,则加上一个等差数列,仍为等差数列。将等差数列的和加入B[j],将首项加入Bh[j],将公差
加入Bd[j]。 若区间中有部分i被某个桶部分包括,则计算出加入的值逐个加入A[i],同时加入桶内和B[i / S],。
求和时,若求和区间完全包括某个桶j,则直接将桶内和B[j]加入和。若某些区间中的i被某个桶j部分包括,则根据桶首项Bh[j]和桶公差Bd[j]计算出该值,逐个加入结果。
复杂度为O(S+N/S+S),S为桶的容量。
代码如下
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#define MAX_N 100005
using namespace std;
typedef long long LL;
const int sib=240;
int N,M;
LL A[100005],B[1000],Bhead[1000],Bdif[1000];
void add(int a,int b,LL x,LL d)
{
int i,j;
int bl=a/sib,br=b/sib;
LL res=0;
for(i=a;i<bl*sib+sib&&i<=b;i++){
A[i]=A[i]+x+(i-a)*d;
B[i/sib]+=x+(i-a)*d;
res+=d;
}
for(j=bl+1;j<br;j++){
Bhead[j]=Bhead[j]+x+res;
Bdif[j]=Bdif[j]+d;
B[j]+=(x+res)*sib+sib*(sib-1)/2*d;
res+=sib*d;
}
if(bl!=br)
for(i=br*sib;i<=b;i++){
B[i/sib]+=x+d*(i-a);
A[i]+=x+d*(i-a);
}
}
LL sum(int a,int b)
{
int i,j;
int bl=a/sib,br=b/sib;
LL res=0;
for(i=a;i<(bl+1)*sib&&i<=b;i++){
res+=A[i];
res+=Bhead[bl]+(i%sib)*Bdif[bl];
}
for(i=bl+1;i<br;i++){
res+=B[i];
}
if(br!=bl)
for(i=br*sib;i<=b;i++){
res+=A[i];
res+=Bhead[br]+(i%sib)*Bdif[br];
}
return res;
}
int main()
{
int i,j,k,T,l,r,flag,x,d;
scanf("%d",&T);
while(T--){
scanf("%d%d",&N,&M);
memset(B,0,sizeof(B));
memset(Bhead,0,sizeof(Bhead));
memset(Bdif,0,sizeof(Bdif));
for(i=0;i<N;i++){
scanf("%lld",&A[i]);
B[i/sib]+=A[i];
}
for(i=0;i<M;i++){
scanf("%d %d %d",&flag,&l,&r);
if(flag==1){
scanf("%d %d",&x,&d);
add(l-1,r-1,(LL)x,(LL)d);
}
else if(flag==2){
printf("%lld\n",sum(l-1,r-1));
}
}
}
return 0;
}
树状数组:
核心是构造出公式,然后就可以直接套模板
设S【i】是修改区间【l,r】前的前i项和,S’【i】是修改后的前i项和,首项为x,公差为d。易得
S‘[i] = S[i] + ( x + x + ( i -l ) * d) * ( i- l+1 ) / 2; (l <=i <= r)
S'[i] = S[i] + ( x + x + ( r -l ) * d) * (r- l+1 ) / 2; ( i > r)
将公式化简得2*S'[i] = 2*S[i] + i*i*d +(d-2*l*d+2*x)*i+l*l*d-l*d-2*l*x+2*x;
故我们可以用3个bit数组来保存结果
令sum(*b,i)为数组*b的前i项和 则S[i] =sum(bit0, i)*i*i+sum(bit1,i)*i+sum(bit2,i);
那么在[l,r]区间上依次加上(x,x+d,······)就等于
在bit2的l上加上l*l*d-l*d-2*l*x+2*x,在r+1中上加上r*r*d+(2*x-2*l*d+d)*r;
在bit1的l上加上2*x-2*l*d+d, 在r+1上加上-2*x+2*l*d-d;
在bit0的l上加上d,在r+1上加上-d;
然后直接套模板就可以计算了
代码如下:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#define MAX_N 100010
using namespace std;
typedef long long LL;
int M;
LL N,A[MAX_N];
LL bit0[MAX_N],bit1[MAX_N],bit2[MAX_N];
LL sum(LL *b,LL i)
{
LL s=0;
while(i>0){
s+=b[i];
i-=i&-i;
}
return s;
}
void add(LL* b,LL i,LL v)
{
while(i<=N){
b[i]+=v;
i+=i&-i;
}
}
int main()
{
int k,T,x,d,flag;
LL l,r;
scanf("%d",&T);
while(T--){
scanf("%lld %d",&N,&M);
memset(bit0,0,sizeof(bit0));
memset(bit1,0,sizeof(bit1));
memset(bit2,0,sizeof(bit2));
for(LL i=1;i<=N;i++){
scanf("%lld",&A[i]);
add(bit2,i,2*A[i]);
}
for(int i=0;i<M;i++){
scanf("%d %lld %lld",&flag,&l,&r);
if(flag==1){
scanf("%d %d",&x,&d);
add(bit0,l,d);
add(bit0,r+1,-d);
add(bit1,l,2*x-2*l*d+d);
add(bit1,r+1,-2*x+2*l*d-d);
add(bit2,l,l*l*d-l*d-2*l*x+2*x);
add(bit2,r+1,r*r*d+(2*x-2*l*d+d)*r);
/* for(j=l;j<=r;j++)
A[j]+=x+(j-l)*d;
for(j=1;j<=N;j++)printf("%d ",A[j]);
printf("\n");*/
}
else if(flag==2){
LL res=0;
res+=sum(bit0,r)*r*r+sum(bit1,r)*r+sum(bit2,r);
res-=sum(bit0,l-1)*(l-1)*(l-1)+sum(bit1,l-1)*(l-1)+sum(bit2,l-1);
printf("%lld\n",res/2);
}
}
}
return 0;
}