懒标记基本原则:
1、如果当前区间被完全覆盖在目标区间里,讲这个区间的sum+k*(tree[i].r-tree[i].l+1)
2、如果没有完全覆盖,则先下传懒标记
3、如果这个区间的左儿子和目标区间有交集,那么搜索左儿子
4、如果这个区间的右儿子和目标区间有交集,那么搜索右儿子
代码示例:
//洛谷P3372
#include<iostream>
#include<queue>
#include<string>
using namespace std;
typedef long long ll;
const int N=1e5+10;
ll n,m;
ll a[N];
struct Tree{
ll l,r;
ll sum; //维护值为区间和
ll lz;
}tr[N*4];
void pushup(int p){
tr[p].sum=tr[p<<1].sum+tr[p<<1|1].sum;
}
void pushdown(int p){
//消除懒标记
if(tr[p].lz){
tr[p<<1].lz+=tr[p].lz;
tr[p<<1|1].lz+=tr[p].lz;
tr[p<<1].sum+=(tr[p<<1].r-tr[p<<1].l+1)*tr[p].lz;
tr[p<<1|1].sum+=(tr[p<<1|1].r-tr[p<<1|1].l+1)*tr[p].lz;
tr[p].lz=0;
}
}
void build(int p,ll l,ll r){
//建树
tr[p]={l,r,0,0};
if(l==r){
tr[p]={l,r,a[r],0};
return ;
}
int mid=l+r>>1;
build(p<<1,l,mid);
build(p<<1|1,mid+1,r);
pushup(p);
}
void modify(int p,ll l,ll r,ll v){
//使区间l到r之间每个元素都加上v
if(tr[p].l>=l&&tr[p].r<=r){
tr[p].sum+=(tr[p].r-tr[p].l+1)*v;
tr[p].lz+=v;//懒标记
return ;
}
pushdown(p);//pushdown更新修改后区间的值
ll mid=tr[p].l+tr[p].r>>1;
if(l<=mid) modify(p<<1,l,r,v);
if(r>mid) modify(p<<1|1,l,r,v);
pushup(p);
}
ll query(int p,ll l,ll r){
//查询区间l到r之间所有元素的和
if(tr[p].r<l||tr[p].l>r)
return 0;
if(tr[p].l>=l&&tr[p].r<=r)
return tr[p].sum;
pushdown(p);
ll res=0;
res+=query(p<<1,l,r);
res+=query(p<<1|1,l,r);
return res;
}
int main()
{
cin>>n>>m;
for(int i=1;i<=n;++i){
cin>>a[i];
}
build(1,1,n);
while(m--){
int opt,left,right,k;
cin>>opt;
if(opt==1){
cin>>left>>right>>k;
modify(1,left,right,k);
}
else{
cin>>left>>right;
cout<<query(1,left,right)<<endl;
}
}
return 0;
}
P3373 【模板】线段树 2
此题为需要区间乘法操作,进行区间乘法操作时,应将之前的加法标记也要乘以v
#include<iostream>
#include<queue>
#include<string>
using namespace std;
typedef long long ll;
const int N=1e5+10;
ll n,m,pp;
ll a[N];
struct Tree{
ll l,r;
ll sum; //维护值为区间和
ll lz;
ll lz2;
}tr[N*4];
void pushup(int p){
tr[p].sum=(tr[p<<1].sum+tr[p<<1|1].sum)%pp;
}
void pushdown(int p){
//消除懒标记
Tree &root=tr[p],&left=tr[p<<1],&right=tr[p<<1|1];
if(root.lz||root.lz2!=1){
left.lz=(left.lz*root.lz2+root.lz)%pp;
right.lz=(right.lz*root.lz2+root.lz)%pp;
left.lz2=left.lz2*root.lz2%pp;
right.lz2=right.lz2*root.lz2%pp;
left.sum=(left.sum*(root.lz2%pp)+(left.r-left.l+1)*root.lz)%pp;
right.sum=(right.sum*root.lz2+(right.r-right.l+1)*root.lz)%pp;
root.lz=0;
root.lz2=1;
}
}
void build(int p,ll l,ll r){
//建树
tr[p]={l,r,0,0,1};
if(l==r){
tr[p]={l,r,a[r],0,1};
return ;
}
int mid=l+r>>1;
build(p<<1,l,mid);
build(p<<1|1,mid+1,r);
pushup(p);
}
void modify(int p,ll l,ll r,ll v){
//使区间l到r之间每个元素都加上v
if(tr[p].l>=l&&tr[p].r<=r){
tr[p].sum=(tr[p].sum+(tr[p].r-tr[p].l+1)*v%pp)%pp;
tr[p].lz+=v;//懒标记
return ;
}
pushdown(p);//pushdown更新修改后区间的值
ll mid=tr[p].l+tr[p].r>>1;
if(l<=mid) modify(p<<1,l,r,v);
if(r>mid) modify(p<<1|1,l,r,v);
pushup(p);
}
void modify2(int p,ll l,ll r,ll v){
//将区间l到r之间每个数都乘以v
if(tr[p].l>=l&&tr[p].r<=r){
tr[p].sum=(tr[p].sum*v)%pp;
tr[p].lz2=tr[p].lz2*v%pp;
tr[p].lz=tr[p].lz*v%pp;//如果在乘以v操作之前已经进行过加法操作,则应将加法标记也乘以v
return ;
}
pushdown(p);
ll mid=tr[p].l+tr[p].r>>1;
if(l<=mid) modify2(p<<1,l,r,v);
if(r>mid) modify2(p<<1|1,l,r,v);
pushup(p);
}
ll query(int p,ll l,ll r){
//查询区间l到r之间所有元素的和
if(tr[p].r<l||tr[p].l>r)
return 0;
if(tr[p].l>=l&&tr[p].r<=r)
return tr[p].sum;
pushdown(p);
ll res=0;
res=(res+query(p<<1,l,r))%pp;
res=(res+query(p<<1|1,l,r))%pp;
return res;
}
int main()
{
cin>>n>>m>>pp;
for(int i=1;i<=n;++i){
cin>>a[i];
}
build(1,1,n);
while(m--){
ll x,y,k,opt;
cin>>opt;
if(opt==1){
cin>>x>>y>>k;
modify2(1,x,y,k);
}
else if(opt==2){
cin>>x>>y>>k;
modify(1,x,y,k);
}
else if(opt==3){
cin>>x>>y;
cout<<query(1,x,y)<<endl;
}
}
return 0;
}