多组数据(n,m同为0时结束),每组第一行一个 n 表有n个整数,一个 m 表有m条操作(1<=n,m<=1e5)
接下来 m 行,每行4个整数(1<=x<=y<= n,1<= c<=1e4,1<= p<=3)
1 x y c 表示 x<=i<=y,ai+=c;
2 x y c 表示 x<=i<=y,ai*=c;
3 x y c 表示 x<=i<=y,ai=c;
4 x y p 表示 输出 x<=i<=y ,ai^p的和
在解析之前,我们先来复习一下基本的求和线段树(带lazy标记),然后讲一下我最初的错误思想
基础好且不感兴趣的客官可以考虑跳过这部分内容
首先是复习部分
线段树求和时,遇到完整可更新的结点立刻更新该结点值,并挂上lazy攒着没向下更新的值
不能整段更新则不能挂上lazy,然后把原来的lazy下放(down),更新子结点
递归到最后,再利用子节点更新父结点(up)
query的时候注意不需要up,因为即便有down的更新也是完整结点的更新
接着说说我的错误想法
p==1的时候非常好更新,然而p==2,p==3该怎么办呢,着眼点自然在这里
于是我开始思考了,先考虑p==2
对叶子结点的父结点有 sum2=x1^2+x2^2
而内部结点呢 sum2[rt]=sum2[lson]+sum2[rson]
于是一层一层的想,会不会有什么特别的关系直接能够更新当前结点呢
傻瓜一样的想法,自己写下的这种前提条件就不可能直接更新大结点,只能up
于是就想,那我们就不更新无脑攒lazy,只有要输出值的时候再一口气去掉lazy,down下去再up回来
真是糟糕的想法,这样和没lazy甚至都快要差不多了(不过听说有人用这个写7500ms卡过去了。。。似乎不优化常数铁定过不了)
怎么说呢,不要有奇怪且错误的想法,还是应当往线段树原有的lazy的功能上去想,才不会使思路跑偏
本道题关键点与难点就在于update,搞定了,本题就过了
关于update,想一想普通求和线段树就会意识到最关键的点在于,如何直接对当前的完整结点更新
对于普通求和我们是 sum+=(r-l+1)*c,换个写法就是 sum=sum+(r-l+1)*c
于是我们便意识到这种更新就是找 老sum 与 新sum 的关系
而你用 sum[rt]=sum[lson]+sum[rson] 去想是没意义的,我们根本不需涉及子结点
于是怎么办呢,写出sum的最简式来考虑,就不难发现规律了
sum1=x1+x2+...+xn
sum2=x1^2+x2^2+...+xn^2
sum3=x1^3+x2^3+...+xn^3
操作1(+=c):
sum1'=(x1+c)+(x2+c)+...+(xn+c) = sum1+nc
sum2'=(x1+c)^2+(x2+c)^2+...+(xn+c)^2
=x1^2+x2^2+...+xn^2+2c(x1+x2+...+xn)+nc^2
=sum2+2csum1+nc^2
sum3'=(x1+c)^3+(x2+c)^3+...+(xn+c)^3
=x1^3+x2^3+...+xn^3+3c(x1^2+x2^2+...+xn^2)+3c^2(x1+x2+...+xn)+nc^3
=sum3+3csum2+3c^2sum1+nc^3
操作2(*=c):(比较简单了,不写详细过程了)
sum1''=csum1 sum2''=c^2sum2 sum3''=c^3sum3
操作3(=c):
sum1'''=nc sum2'''=nc^2 sum3'''=nc^3
然后需要注意的一点是,写代码的时候操作1 要先更sum3,再sum2,然后sum1
因为这样就能保证等式右侧用的是旧的 sum1 sum2 sum3
如果你不愿意这样写的话也可以自己整个临时变量也行
那么关于lazy的部分,仔细想一下,似乎还有顺序问题,该怎么办呢,比如 +c1 *c2
先+后*则 x1^2 变成 (c2x1+c1c2)^2 先*后+ 则为 (c2x1+c1)^2
难道还要记录先后顺序吗?没必要!
如果是先来的 +c2 那么把 对应的lazy乘一下变成 lazy1*c1 即 c1*c2
用的时候 x1先做乘 再做加就好
对于操作三赋值的部分呢?读者可以自己想一下,也不难发现应该怎么做
于是结论如下:
操作1:lazy1+=c
操作2:lazy1*=c lazy2*=c
操作3:lazy1=0 lazy2=1 lazy3=c
这里千万要注意 lazy2 要初始化为 1,博主就犯了错误初始化为0了
对操作2 lazy2*=c 要是0的话怎么*都是0,不过非要用0也不是不可以,特判一下就可以了
但是 很容易犯错,请一定要注意
然后 down 的时候什么样的顺序 用lazy去更新子结点呢
我们知道操作3更新时 lazy1,2被初始化了,于是此时若 lazy1,2有值,则是lazy3之后 后来的
于是down的时候
先更 lazy3 再更 lazy2 最后更 lazy1
然后此处也有非常重要的一点,把当前lazy给到子结点lazy的时候
因为子结点lazy可能已经有过值,一定不要把父节点lazy就直接赋给了子结点lazy
一定要按正常套路再去算一遍
不得不说这道题细节是真的多
说到细节。。博主不小心犯了个傻瓜式错误,线段树空间忘开四倍了。。。。
结果报错居然报的不是RE而是TLE。。debug了半天。。。真的怀疑人生,为什么会犯这种错误。。。
然后关于更新,我看网上好多题解都是 lazy1,2混更,我不知道为什么要把代码和思路都搞的那么乱,反正三个依次来就好
这些搞定了这道题基本就搞定了,客官不需要下面的代码也足以自己写出来了
说实话这个题代码量还是挺大的,我的代码写的也蛮乱套的(笑哭),不想看的话就不看也行,反正知道了思路就可以自己写了
代码如下(为了使代码看着更清爽,加了些引用):
#pragma GCC optimize("Ofast")
#include
#define M 10007
#define maxn 100005
using namespace std;
int n,m,op,x,y,c,tree1[maxn<<2],tree2[maxn<<2],tree3[maxn<<2],lazy1[maxn<<2],lazy2[maxn<<2],lazy3[maxn<<2];
void build(int l,int r,int rt){
tree1[rt]=tree2[rt]=tree3[rt]=lazy1[rt]=lazy3[rt]=0;
lazy2[rt]=1;
if(l==r) return;
build(l,(l+r)/2,rt<<1);
build((l+r)/2+1,r,rt<<1|1);
}
void down(int l,int r,int rt){
int ln=(l+r)/2-l+1,rn=r-(l+r)/2,c1=lazy1[rt],c2=lazy2[rt],c3=lazy3[rt];
int &l1=tree1[rt<<1],&l2=tree2[rt<<1],&l3=tree3[rt<<1];
int &r1=tree1[rt<<1|1],&r2=tree2[rt<<1|1],&r3=tree3[rt<<1|1];
if(c3){
l1=ln*c3%M,r1=rn*c3%M;
l2=ln*c3%M*c3%M,r2=rn*c3%M*c3%M;
l3=ln*c3%M*c3%M*c3%M,r3=rn*c3%M*c3%M*c3%M;
lazy1[rt<<1]=lazy1[rt<<1|1]=0;
lazy2[rt<<1]=lazy2[rt<<1|1]=1;
lazy3[rt<<1]=lazy3[rt<<1|1]=c3;
}
if(c2>1){
l1=l1*c2%M,r1=r1*c2%M;
l2=l2*c2%M*c2%M,r2=r2*c2%M*c2%M;
l3=l3*c2%M*c2%M*c2%M,r3=r3*c2%M*c2%M*c2%M;
lazy1[rt<<1]=lazy1[rt<<1]*c2%M,lazy1[rt<<1|1]=lazy1[rt<<1|1]*c2%M;
lazy2[rt<<1]=lazy2[rt<<1]*c2%M,lazy2[rt<<1|1]=lazy2[rt<<1|1]*c2%M;
}
if(c1){
l3=(l3+3*c1*l2%M+3*c1*c1%M*l1%M+ln*c1%M*c1%M*c1%M)%M;
r3=(r3+3*c1*r2%M+3*c1*c1%M*r1%M+rn*c1%M*c1%M*c1%M)%M;
l2=(l2+2*c1*l1%M+ln*c1%M*c1%M)%M;
r2=(r2+2*c1*r1%M+rn*c1%M*c1%M)%M;
l1=(l1+ln*c1%M)%M;
r1=(r1+rn*c1%M)%M;
lazy1[rt<<1]=(lazy1[rt<<1]+c1)%M,lazy1[rt<<1|1]=(lazy1[rt<<1|1]+c1)%M;
}
lazy1[rt]=lazy3[rt]=0,lazy2[rt]=1;
}
void up(int rt){
tree1[rt]=(tree1[rt<<1]+tree1[rt<<1|1])%M;
tree2[rt]=(tree2[rt<<1]+tree2[rt<<1|1])%M;
tree3[rt]=(tree3[rt<<1]+tree3[rt<<1|1])%M;
}
void update(int op,int x,int y,int c,int l,int r,int rt){
if(ry) return;
if(x<=l&&r<=y){
int &t1=tree1[rt],&t2=tree2[rt],&t3=tree3[rt],len=r-l+1;
if(op==1){
t3=(t3+3*c*t2%M+3*c*c%M*t1%M+len*c%M*c%M*c%M)%M;
t2=(t2+2*c*t1%M+len*c%M*c%M)%M;
t1=(t1+len*c%M)%M;
lazy1[rt]=(lazy1[rt]+c)%M;
}
else if(op==2){
t1=t1*c%M;
t2=t2*c%M*c%M;
t3=t3*c%M*c%M*c%M;
lazy1[rt]=lazy1[rt]*c%M,lazy2[rt]=lazy2[rt]*c%M;
}
else{
t1=len*c%M;
t2=len*c%M*c%M;
t3=len*c%M*c%M*c%M;
lazy1[rt]=0,lazy2[rt]=1,lazy3[rt]=c;
}
return;
}
down(l,r,rt);
update(op,x,y,c,l,(l+r)/2,rt<<1);
update(op,x,y,c,(l+r)/2+1,r,rt<<1|1);
up(rt);
}
int query(int x,int y,int p,int l,int r,int rt){
if(ry) return 0;
if(x<=l&&r<=y) switch(p){
case 1:return tree1[rt];
case 2:return tree2[rt];
case 3:return tree3[rt];
}
down(l,r,rt);
return (query(x,y,p,l,(l+r)/2,rt<<1)+query(x,y,p,(l+r)/2+1,r,rt<<1|1))%M;
}
int main(){
while(scanf("%d%d",&n,&m),n||m){
build(1,n,1);
while(m--){
scanf("%d%d%d%d",&op,&x,&y,&c);
if(op==4) printf("%d\n",query(x,y,c,1,n,1));
else update(op,x,y,c,1,n,1);
}
}
return 0;
}
第二种方法相对来讲各方面都占有优势,代码量很小,速度很快,空间复杂度也很小
但是算是一种投机取巧的方法
很像染色但不是标准染色,能想到这么做也真是不容易
不过再怎么说也是投机取巧,换个题说不好就会 T
这个题毕竟初始全是0,每次更也是一大段一更全是相同的,所以才会有染色的可能性
具体不多解释了,因为代码很简单,直接看代码就好
#pragma GCC optimize("Ofast")
#include
#define M 10007
using namespace std;
int n,m,op,x,y,c,tree[100005<<2];
void down(int rt){
if(tree[rt]==-1) return;
tree[rt<<1]=tree[rt<<1|1]=tree[rt];
tree[rt]=-1;
}
void subupdate(int op,int c,int l,int r,int rt){
if(tree[rt]>=0){
if(op==1) tree[rt]=(tree[rt]+c)%M;
else tree[rt]=tree[rt]*c%M;
return;
}
subupdate(op,c,l,(l+r)/2,rt<<1);
subupdate(op,c,(l+r)/2+1,r,rt<<1|1);
}
void update(int op,int x,int y,int c,int l,int r,int rt){
if(ry) return;
if(x<=l&&r<=y){
if(op==3) tree[rt]=c;
else subupdate(op,c,l,r,rt);
return;
}
down(rt);
update(op,x,y,c,l,(l+r)/2,rt<<1);
update(op,x,y,c,(l+r)/2+1,r,rt<<1|1);
}
int query(int x,int y,int p,int l,int r,int rt){
if(ry) return 0;
if(tree[rt]>=0){
int ret=min(y,r)-max(x,l)+1;
for(int i=1;i<=p;i++) ret=ret*tree[rt]%M;
return ret;
}
return (query(x,y,p,l,(l+r)/2,rt<<1)+query(x,y,p,(l+r)/2+1,r,rt<<1|1))%M;
}
int main(){
while(scanf("%d%d",&n,&m),n||m){
tree[1]=0;
while(m--){
scanf("%d%d%d%d",&op,&x,&y,&c);
if(op==4) printf("%d\n",query(x,y,c,1,n,1));
else update(op,x,y,c,1,n,1);
}
}
return 0;
}