如果我既有区间乘法又有区间加法,我应该怎么办呢?
这时候需要写两个标记。假设只写一个标记。
标记加法:此时对于乘法操作,因为是将 \(t_i+lazy_i\) 乘以 \(x\),这样子显然一个懒惰标记做不到。
标记乘法:那我加法咋办?
那两个标记怎么用呢?首先假设加法标记为 \(lazy\),乘法标记为 \(multi\)。如果将这两个标记分开显然是不可能的,因为每个点的标记是互相依赖的。而如果一直 \(push\_down\) 需要花费 \(\log n\) 的时间,这与线段树 \(\log n\) 的标准就不一样了,别忘了还有线段树本身的操作。如果仅仅是给 \(now\) 下放,那么子结点会遇到一样的问题。一般我们做题用的线段树数量是根据查询的种类来的。假设这里只有区间加法查询,那么应该怎么做呢?
首先考虑 \(t\) 的变化,显然在加法操作时,应当 \(t_{now}+=x\times (tr-tl+1)\),在乘法操作时,应当 \(t_{now}\times =x\)。其次,考虑 \(push\_down\),对于加法的 \(push\_down\),和普通的加法下放一样,因为加法下方是不考虑乘法标记的,而乘法标记需要考虑加法标记。如果反过来,加法下放考虑乘法标记,这时候 \(lazy_{now}\) 就可能出现分数,就不太方便了。那么考虑乘法下放。对于一个区间,如果统统乘以 \(x\),那么这个区间的和就乘以 \(x\),这是乘法分配律。所以,乘法标记下放的时候应该让 \(t_{now*2(+1)}\) 和 \(lazy\)_{now*2(+1)} 都乘以 \(multi_{now}\)。那么如果将一个区间都乘以 \(x\) 再都乘以 \(y\),就相当于乘以 \(xy\),所以 \(mutli_{now*2(+1)}\) 应当乘以 \(multi_{now}\)。然后将 \(multi_now\) 清为 \(1\)。在写修改和查询的时候,要先下放乘法标记,再下放加法标记,因为乘法标记包含对加法的考虑,所以先下放加法会出现各种各样的问题。
以下代码参考题目:
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e5+10;
int n,p,m,a[N],t[N*8],lazy[N*8],multi[N*8];
void push_down_add(int now,int tl,int tr){
int mid=(tl+tr)/2;
t[now*2]=(t[now*2]+lazy[now]*(mid-tl+1))%p;
t[now*2+1]=(t[now*2+1]+lazy[now]*(tr-mid))%p;
lazy[now*2]=(lazy[now*2]+lazy[now])%p;
lazy[now*2+1]=(lazy[now*2+1]+lazy[now])%p;
lazy[now]=0;
}
void push_down_multi(int now,int tl,int tr){
int mid=(tl+tr)/2;
t[now*2]=t[now*2]*multi[now]%p;
t[now*2+1]=t[now*2+1]*multi[now]%p;
multi[now*2]=multi[now*2]*multi[now]%p;
multi[now*2+1]=multi[now*2+1]*multi[now]%p;
lazy[now*2]=lazy[now*2]*multi[now]%p;
lazy[now*2+1]=lazy[now*2+1]*multi[now]%p;
multi[now]=1;
}
void build(int now,int tl,int tr){
multi[now]=1;
if(tl==tr){
t[now]=a[tl];
return ;
}
int mid=(tl+tr)/2;
build(now*2,tl,mid);
build(now*2+1,mid+1,tr);
t[now]=(t[now*2]+t[now*2+1])%p;
}
void modify_add(int now,int tl,int tr,int l,int r,int x){
if(tl>=l&&tr<=r){
t[now]=(t[now]+x*(tr-tl+1))%p;
lazy[now]=(lazy[now]+x)%p;
return ;
}
if(tl>r||tr<l){
return ;
}
if(multi[now]!=1){
push_down_multi(now,tl,tr);
}
if(lazy[now]){
push_down_add(now,tl,tr);
}
int mid=(tl+tr)/2;
modify_add(now*2,tl,mid,l,r,x);
modify_add(now*2+1,mid+1,tr,l,r,x);
t[now]=(t[now*2]+t[now*2+1])%p;
}
void modify_multi(int now,int tl,int tr,int l,int r,int x){
if(tl>=l&&tr<=r){
t[now]=t[now]*x%p;
multi[now]=multi[now]*x%p;
lazy[now]=lazy[now]*x%p;
return ;
}
if(tl>r||tr<l){
return ;
}
if(multi[now]!=1){
push_down_multi(now,tl,tr);
}
if(lazy[now]){
push_down_add(now,tl,tr);
}
int mid=(tl+tr)/2;
modify_multi(now*2,tl,mid,l,r,x);
modify_multi(now*2+1,mid+1,tr,l,r,x);
t[now]=(t[now*2]+t[now*2+1])%p;
}
int query(int now,int tl,int tr,int l,int r){
if(tl>=l&&tr<=r){
return t[now];
}
if(tl>r||tr<l){
return 0;
}
if(multi[now]!=1){
push_down_multi(now,tl,tr);
}
if(lazy[now]){
push_down_add(now,tl,tr);
}
int mid=(tl+tr)/2;
return (query(now*2,tl,mid,l,r)+query(now*2+1,mid+1,tr,l,r))%p;
}
signed main(){
//freopen("xx.in","r",stdin);
//freopen("xx.out","w",stdout);
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin>>n>>m>>p;
for(int i=1;i<=n;i++){
cin>>a[i];
}
build(1,1,n);
while(m--){
int opt,t,g,c;
cin>>opt;
if(opt==1){
cin>>t>>g>>c;
modify_multi(1,1,n,t,g,c);
}else if(opt==2){
cin>>t>>g>>c;
modify_add(1,1,n,t,g,c);
}else{
cin>>t>>g;
cout<<query(1,1,n,t,g)<<"\n";
}
}
return 0;
}
标签:multi,int,线段,tr,叠加,tl,区间,lazy,now
From: https://www.cnblogs.com/wuyiming1263/p/18377184