BOJ451(桶分割/树状数组) 区间修改



原题大概意思是,维护一个长为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;
}




  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值