【树状数组】MZOJ1090:Crazy

CRAZY

题目描述

区间长度为n
设其为a[1]~a[n]
初始值均为0
请你支持如下操作:
0 l w 给a[l]a[l]加ww
1 l r w 给a[l],a[l+1]…a[r]a[l],a[l+1]…a[r]加ww
2 l r w d 给a[l],a[l+1]…a[r]a[l],a[l+1]…a[r]分别加上w,w+d,w+2d,…,w+(r−l)dw,w+d,w+2d,…,w+(r−l)d
-1 l 输出a[l]
-2 l r 输出sum(a[l]~a[r]) 即∑i=lra[i]

输入

先输入n,m,typ表示区间长度,操作数,以及数据的约定类型(见下表)
接下来m行,每行一个操作,形如以上所述,其中-2 ~ 2表示操作类型

输出

对每个输出操作,输出一行表示答案
保证答案不超过2^63-1

数据范围

测试点n,m的值约定
11e5类型1
21e6类型1
31e5类型2
41e6类型2
5,6,7,81e5类型3
9,101e6类型3

其中:
类型1:无1,2操作
类型2:无2操作
类型3:无限制

样例

in:
5 6 3
1 1 4 2
0 5 -3
-2 4 5
0 2 2
2 1 2 5 3
-1 2
out:
-1
12

错解线段树

第一次看这道题,脑中闪过的想法就是线段树,两个lazy标记lazyf,lazye分别表示当前区间第一个数要加的lazy值和最后一个数要加的lazy值。因为区间修改只能加相同一个数或者是一个等差数列,那么在pushdown之前lazy就一定是一个等差数列。
但是这么维护却一直超时,求大佬找错qaq。

#include<stdio.h>
const int maxn= 1e6;
long long tree[maxn*4+5],lazyf[maxn*4+5],lazye[maxn*4+5];
int read()
{
    int x=0;char c=getchar(),f=1;
    while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
    while(c<='9'&&c>='0')x=10*x+c-'0',c=getchar();
    return f*x;
}
long long adde1(int l,int r,int i,int w,int a)
{
    int temp=(l+r)/2;
    if(a<=l&&a>=r)
    {
        tree[i]+=w;
    }
    if(a<l||a>r)
    return 0;
    adde1(l,temp,i*2,w,a);
    adde1(temp+1,r,i*2+1,w,a);
    if(l==r) return 0;
}
long long pushdown(int l,int r,int i,int temp)
{
    if(lazyf[i]==lazye[i]&&lazyf[i]!=0)
    {
        lazyf[i<<1|1]+=lazyf[i];
        lazye[i<<1|1]+=lazye[i];
        lazyf[i<<1]+=lazyf[i];
        lazye[i<<1]+=lazye[i];
        tree[i<<1|1]+=(r-temp)*lazyf[i];
        tree[i<<1]+=(temp-l+1)*lazyf[i];
        lazyf[i]=0;
        lazye[i]=0;
    }
    if(lazyf[i]!=lazye[i])
    {
        int data=(lazye[i]-lazyf[i])/(r-l);
        int left=lazyf[i]+data*((r-l)>>1);
        int right=lazye[i]-data*((r-l)>>1);
        lazyf[i<<1]+=lazyf[i];
        lazye[i<<1]+=left;
        lazyf[i<<1|1]+=right;
        lazye[i<<1|1]+=lazye[i];
        tree[i<<1|1]+=(right+lazye[i])*(r-temp)>>1;
        tree[i<<1]+=(lazyf[i]+left)*(temp-l+1)>>1;
        lazyf[i]=0;
        lazye[i]=0;     
    }
}
long long adde2(int l,int r,int i,int w,int a,int b)
{       
    int temp=(l+r)>>1;
    if(b<l||a>r)
    return 0;
    if(a<=l&&b>=r)
    {
        lazyf[i]+=w;
        lazye[i]+=w;
        tree[i]+=(r-l+1)*w;
        return (r-l+1)*w;
    }
    else
    {
        pushdown(l,r,i,temp);
        long long ans1=adde2(l,temp,i<<1,w,a,b);
        long long ans2=adde2(temp+1,r,i<<1|1,w,a,b); 
        tree[i]+=ans1+ans2;
        return ans1+ans2;
    }
}
long long adde3(int l,int r,int i,int w,int a,int b,int data)
{     
    int temp=(l+r)>>1;
    if(b<l||a>r)
    return 0;
    if(a<=l&&b>=r)
    {
        lazyf[i]+= w;
        lazye[i]+= w+data*(r-l);
        tree[i]+= (w+w+data*(r-l))*(r-l+1)>>1;
        return (w+w+data*(r-l))*(r-l+1)>>1;
    }
    else
    {
        pushdown(l,r,i,temp);
        int right=w+data*(r-l+1)>>1;
        int ans1=adde3(l,temp,i<<1,w,a,b,data);
        int ans2=adde3(temp+1,r,i<<1|1,right,a,b,data);
        tree[i]+=ans1+ans2;
        return ans1+ans2;
    }
}
long long q1(int l,int r,int i,int a,int b)
{
    int temp=(l+r)>>1;    
    if(b<l||a>r)
    return 0;
    if(a<=l&&b>=r)
    {
        return tree[i];
    }
    else
    {
        pushdown(l,r,i,temp);
        return q1(l,temp,i<<1,a,b)+q1(temp+1,r,i<<1|1,a,b);
    }
}
int main()
{
    int n,m,tpy,i,j;
    n=read();
    m=read();
    tpy=read();
    while(m--)
    {
        int t,l,w,d,r;
        t=read();
        if(t==0)
        {
            l=read(),w=read();
            adde2(1,n,1,w,l,l);

        }
        else if(t==1)
        {
            l=read(),r=read(),w=read();
            adde2(1,n,1,w,l,r);
        }
        else if(t==2)
        {
            l=read(),r=read(),w=read(),d=read();
            adde3(1,n,1,w,l,r,d);
        }
        else if(t==-1)
        {
            l=read();
            printf("%lld\n",q1(1,n,1,l,l));
        }
        else if(t==-2)
        {
            l=read(),r=read();
            printf("%lld\n",q1(1,n,1,l,r));
        }
    }
    return 0;
}

正解 树状数组+bit维护(大佬的题解)

考虑一下之后发现线段树写区间加等差序列并不是很好写

于是上bit233

先考虑将区间修改转化为单点修改

如果转化成功了,那么由于只有单点修改所以线段树就没啥卵用了,上bit

考虑怎样用树状数组实现区间加法区间求和:

由于需要区间求和,因此我们需要维护前缀和来实现区间查询(bit自带此功能)

通常的bit在维护前缀和时只能实现单点修改

也就是说如果把前缀和看成函数(即sum(1,i)关于i的函数)的话

通常bit只能实现对该函数的某个后缀加上一个常值函数,可以看成对该后缀的值整体抬升

而我们区间修改相当于是两次后缀修改

每一次加的不是一个常值函数而是一个一次函数

怎么办呢,之前的那颗bit只能整体修改一个后缀的零次项而不是一次项

那就再开一颗bit维护一次项就好了啊

具体怎么做?

设维护0次函数的bit为b0,1次的为b1,我们要对[L,R]加w

则将b0的L位置及其后面的位置-(L-1)*w ( 即 b0.add(L,-(L-1)*w) )

b1的L位置及其后+w

b0的R+1位置及其后+(R-l)*w

b1的R+1位置及其后+w

修改就结束了

查询[L,R]?

int L0=b0.query(L-1),L1=b1.query(L-1),R0=b0.query(R),R1=b1.query(R),xl=L-1,xr=R;

ans=(xr*R1+R0) - (xl*L1+L0);

即让两个x对应的y相减即可

那么就完事了

那么区间加等差怎么做?

再加一颗bit维护二次项系数就好了啊(你还可以加二次等差,加三次等差,只要愿意码…)

以[L,R] +w,+w+d,+w+2d,…,+w+(R-L)*d 为例

画一下柿子之后发现应该:

b0.add(L,w*(1-L)+d*L*(L-1)/2)

b1.add(L,w-L*d+d/2)

b2.add(L,d/2)

“/2”不好弄就全局*2

R+1位置类似这里就不详写了

查询也就不写了吧

于是我们就用三颗bit维护了区间加等差

终于完了

代码

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1e6+5;
int n,m,Case;
ll b[3][N],a[3];
int read(){
    int x=0;char c=getchar(),f=1;
    while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
    while(c<='9'&&c>='0')x=10*x+c-'0',c=getchar();
    return f*x;
}
ll query(int x)
{
    ll *b0=b[0],*b1=b[1],*b2=b[2],c0=0,c1=0,c2=0;
    if(Case==1) for(int i=x;i;i^=i&-i)c0+=b0[i];
    else if(Case==2) for(int i=x;i;i^=i&-i)c0+=b0[i],c1+=b1[i];
    else for(int i=x;i;i^=i&-i)c0+=b0[i],c1+=b1[i],c2+=b2[i];
    return c2*x*x+c1*x+c0;
}
void add(int p,ll c0,ll c1,ll c2){
    ll *b0=b[0],*b1=b[1],*b2=b[2];
    if(!c2)
        if(!c1) while(p<=n)b0[p]+=c0,p+=p&-p;
        else while(p<=n)b0[p]+=c0,b1[p]+=c1,p+=p&-p;
    else while(p<=n)b0[p]+=c0,b1[p]+=c1,b2[p]+=c2,p+=p&-p;
}
void add0(ll l,ll s)
{add(l,2*s,0,0);}
void add1(ll l,ll s)
{add(l,2*s*(1-l),2*s,0);}
void add2(ll l,ll s,ll d)
{add(l,2*(1-l)*s+d*l*(l-1),2*s+d-2*l*d,d);}
void add(int l,int r,ll s,ll d,int typ)
{
    if(typ==0)add0(l,s);
    else if(typ==1)add1(l,s),add1(r+1,-s);
    else if(typ==2)add2(l,s,d),add2(r+1,-(s+d*(r-l+1)),-d);
}
int main()
{
    n=read(),m=read(),Case=read();
    for(int i=1,typ,l,r,s,d;i<=m;i++)
    {
        typ=read();
        if(typ==0) l=r=read(),s=read();
        else if(typ==1) l=read(),r=read(),s=read();
        else if(typ==2) l=read(),r=read(),s=read(),d=read();
        else if(typ==-1) l=r=read();
        else if(typ==-2) l=read(),r=read(); 
        if(typ>=0) add(l,r,s,d,typ);
        else if(typ<0) printf("%lld\n",(query(r)-query(l-1))/2);
    }
    return 0;
}

至今无法完全弄懂。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值