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的值 | 约定 |
---|---|---|
1 | 1e5 | 类型1 |
2 | 1e6 | 类型1 |
3 | 1e5 | 类型2 |
4 | 1e6 | 类型2 |
5,6,7,8 | 1e5 | 类型3 |
9,10 | 1e6 | 类型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;
}
至今无法完全弄懂。