一些线段树的前缀知识需要掌握,推荐博客https://www.cnblogs.com/TheRoadToTheGold/p/6254255.html(机房最强学姐)
这里拿一道板子题写一下 luogu 【模板】P3373线段树2
这道题与之前的那些线段树水题所不同的是,它用到了区间乘,也就是说,在某一段区间进行乘法操作。
看到这题我的潜意识就告诉我,需要两个下传操作,也就是两个pushdown函数(在代码里为了简单就直接写了down),然后设置两个懒标记,应该是可以直接过这道题。可是我又转念一想,我可不可以放在一个pushdown函数里写呢?这样既方便又快捷。于是。。。我想不出来了。
在经过看题解后的反复推敲与琢磨之后,我总算是懂得了这个思路。
首先大家知道乘法的优先级大于加法,所以我们在顺序上应该有个先后,算乘法时需要先算乘法再算加法,而算加法直接算加法即可。这段话你们感性理解一下,理解很重要。如果真的不懂,不急,往下慢慢读一读你就会自然而然的明白了。
所以我们就开始一个函数一个函数的讲解了。
首先是建树函数,这个并没有什么好说的。
其次就是pushdown函数了,这个函数是这一道题目的关键。
首先每一个孩子的懒标记要继承父亲的懒标记(乘法标记初值为1,加法标记初值为0),那么应该怎么继承呢?这要分别来讨论:
1.乘法标记 。因为乘法的优先级是大于加法的,所以乘法标记不会受到加法标记的影响。所以就直接乘上父亲的乘法标记
2.加法标记 。因为加法是受到乘法的影响的,所以加法标记的增加量为先前加法的标记的父亲乘法标记倍然后在增加父亲的加法标记倍。如果你不懂,请务必看上面的推荐博客,里面有懒标记的专门讲解,请务必学会!!
以上是一种理解的方法,下面还有另一种
(以上图片来自清北学堂)
好了,懒标记的储存已经讲完了,下面开始讲懒标记的应用:设这段区间的和为s,则父亲的乘法标记为mul,这段区间的长度为len,父亲的加法标记为add,那么区间的s=s*mul+add*len,也就是区间先扩大mul倍然后再加上区间长度的add倍,这看起来很显然,也很好理解。那么有人问了,如果是先加法后乘法这可咋整?诶,这可问到地方了,不急,我们慢慢来,后文会有解答。
下面我们就开始讲乘法操作(mul_interval):
乘法操作也没什么好说的,跟其他函数长得类似唯一与众不同的地方,就是它的懒标记叠加部分。首先乘法标记先乘上倍数这没什么好说的,然后后面是加法标记的操作,因为乘法会影响到加法,这个观点已经在前面说过了,所以add也应该乘上倍数,如果不理解就看一下上面的另一种方法,会有提示。之后乘法操作就讲完了。
下面我们就开始讲加法操作(add_interval):
首先加法不会影响乘法,所以这个操作基本没有变化。
最后就是加和操作,这个操作在上面推荐的博客里面有。
最后就是代码了,不懂得可以自己深入的研究一下:
#include<bits/stdc++.h> #define N 100001 #define LL long long using namespace std; struct edge { LL l,r,w,add=0,mul=1; } tree[N<<2]; int a,b,ans,y,n,m,p; void build(int k,int ll,int rr) { tree[k].l=ll,tree[k].r=rr; if(ll==rr) { cin >> tree[k].w; tree[k].w%=p; return; } int m=(ll+rr)>>1; build(k<<1,ll,m); build(k<<1|1,m+1,rr); tree[k].w=(tree[k<<1].w+tree[k<<1|1].w)%p; return; } void down(int k) { tree[k<<1].mul=tree[k<<1].mul*tree[k].mul%p; tree[k<<1|1].mul=tree[k<<1|1].mul*tree[k].mul%p; tree[k<<1].add=(tree[k].mul*tree[k<<1].add+tree[k].add)%p; tree[k<<1|1].add=(tree[k].mul*tree[k<<1|1].add+tree[k].add)%p; tree[k<<1].w=(tree[k<<1].w*tree[k].mul%p+tree[k].add*(tree[k<<1].r-tree[k<<1].l+1)%p)%p; tree[k<<1|1].w=(tree[k<<1|1].w*tree[k].mul%p+tree[k].add*(tree[k<<1|1].r-tree[k<<1|1].l+1)%p)%p; tree[k].mul=1; tree[k].add=0; return; } void mul_interval(int k) { if(tree[k].l>=a&&tree[k].r<=b) { tree[k].w=(tree[k].w*y)%p; tree[k].mul=(tree[k].mul*y)%p; tree[k].add=(tree[k].add*y)%p; return; } down(k); int m=(tree[k].l+tree[k].r)>>1; if(a<=m)mul_interval(k<<1); if(b>m)mul_interval(k<<1|1); tree[k].w=(tree[k<<1].w+tree[k<<1|1].w)%p; return; } void add_interval(int k) { if(tree[k].l>=a&&tree[k].r<=b) { tree[k].add=tree[k].add+y; tree[k].w=(tree[k].w+(tree[k].r-tree[k].l+1)*y)%p; return; } down(k); int m=(tree[k].r+tree[k].l)>>1; if(a<=m)add_interval(k<<1); if(b>m)add_interval(k<<1|1); tree[k].w=(tree[k<<1].w+tree[k<<1|1].w)%p; return; } void ask_interval(int k) { if(tree[k].l>=a&&tree[k].r<=b) { ans+=tree[k].w; ans%=p; return; } down(k); int m=(tree[k].r+tree[k].l)>>1; if(a<=m)ask_interval(k<<1); if(b>m)ask_interval(k<<1|1); } int main() { cin >> n >> m >> p; build(1,1,n); for(int i=1; i<=m; i++) { int c; cin >> c; if(c==1) { cin >> a >> b >> y; mul_interval(1); } if(c==2) { cin >> a >> b >> y; add_interval(1); } if(c==3) { ans=0; cin >> a >> b; ask_interval(1); cout<<ans%p<<endl; } } }