AcWing 算法提高课 懒标记线段树
pushup是由子节点算父节点
对于懒标记线段树,需要有pushdown函数,将修改向下传播。
对于一个懒标记add,其意义为,给以当前节点为根的子树中(不包含自己)的每一个节点加上add。
查询时,需要将祖先节点上的add值累加到当前节点。
实现方法是,在查询过程中,如果需要递归,则将add标记清空,并传播到子节点,即,在查询时加入pushdown。
修改过程中同样需要向下传播,只要是需要向下递归,就要传播。
举例:
这样就可以支持log的区间修改了
模板:维护区间和的懒标记线段树

const int N=100010; struct Node { int l,r; LL sum,add; }tr[N*4]; void pushup(int u) { tr[u].sum=tr[2*u].sum+tr[2*u+1].sum; } void pushdown(int u) { auto &root=tr[u],&left=tr[2*u],&right=tr[2*u+1]; if(tr[u].add) { left.add+=root.add;right.add+=root.add; left.sum+=root.add*(left.r-left.l+1); right.sum+=root.add*(right.r-right.l+1); root.add=0; } } void build(int u,int l,int r) { if(l==r) { tr[u]={l,r,0,0}; } else { tr[u]={l,r,0,0}; int mid=(l+r)/2; build(2*u,l,mid); build(2*u+1,mid+1,r); pushup(u); } } void modify(int u,int l,int r,LL val) { if(l<=tr[u].l&&r>=tr[u].r)//当前节点被完全包含,不再继续向下 { tr[u].sum+=val*(tr[u].r-tr[u].l+1); //修改懒标记 tr[u].add+=val; } else//当前节点的区间过大,需要分裂 { //分裂必须先pushdown pushdown(u); int mid=(tr[u].l+tr[u].r)/2; if(mid>=l) modify(2*u,l,r,val); if(mid+1<=r) modify(2*u+1,l,r,val); //子区间修改后,需要pushup pushup(u); } } LL query(int u,int l,int r) { if(l<=tr[u].l&&r>=tr[u].r)//完全包含,直接返回 { return tr[u].sum; } else//查询子区间 { //分裂操作必须先pushdown pushdown(u); int mid=(tr[u].l+tr[u].r)/2; LL res=0; if(mid>=l) res+=query(2*u,l,r); if(mid+1<=r) res+=query(2*u+1,l,r); return res; } } void YD() { int n,m;cin>>n>>m; build(1,1,n); fore(i,1,n) { int num;cin>>num; modify(1,i,i,num); } while(m--) { char op;cin>>op; if(op=='Q')//查询 { int l,r;cin>>l>>r; cout<<query(1,l,r)<<endl; } if(op=='C')//修改 { int l,r,val;cin>>l>>r>>val; modify(1,l,r,val); } } }
其中,sum维护区间和,add是懒标记,表示其子节点全部加上add
例题:可以进行区间加法和乘法修改,以及查询区间和的懒标记线段树
https://www.acwing.com/problem/content/1279/
重点思路在于维护mul和add两个懒标记,且先乘再加,便于维护。
优化:乘法和加法可以统一乘一个操作,加法就视为先*1,乘法就视为后+0
注意:线段树的节点只向下看,sum维护区间的求和,懒标记维护的是对区间上的每一个点应该的操作。
在pushdown中,先对子节点的区间sum进行相应的修改,然后根据对每个点的影响,再修改子节点的懒标记。
代码:

#include<bits/stdc++.h> using namespace std; typedef long long LL; const int N=100010; int n; LL mod; struct Node { int l,r; LL sum,mul,add; }tr[N*4]; void pushup(int u) { tr[u].sum=tr[2*u].sum+tr[2*u+1].sum; tr[u].sum%=mod; } void mul_add(int u,LL m,LL a) { tr[u].sum=(tr[u].sum*m+a*(tr[u].r-tr[u].l+1))%mod; tr[u].mul=tr[u].mul*m%mod; tr[u].add=(tr[u].add*m+a)%mod; } void pushdown(int u) { mul_add(2*u,tr[u].mul,tr[u].add); mul_add(2*u+1,tr[u].mul,tr[u].add); tr[u].mul=1;tr[u].add=0; } void build(int u,int l,int r) { if(l==r) { tr[u]={l,r,0,1,0}; } else { tr[u]={l,r,0,1,0}; int mid=(l+r)/2; build(u*2,l,mid); build(u*2+1,mid+1,r); pushup(u); } } void modify(int u,int l,int r,LL m,LL a) { if(l<=tr[u].l&&r>=tr[u].r) { mul_add(u,m,a); } else { pushdown(u); int mid=(tr[u].l+tr[u].r)/2; if(l<=mid) modify(2*u,l,r,m,a); if(r>=mid+1) modify(2*u+1,l,r,m,a); pushup(u); } } LL query(int u,int l,int r) { if(l<=tr[u].l&&r>=tr[u].r) return tr[u].sum; else { LL res=0; pushdown(u); int mid=(tr[u].l+tr[u].r)/2; if(l<=mid) res+=query(2*u,l,r); if(r>=mid+1) res+=query(2*u+1,l,r); return res%mod; } } int main() { cin>>n>>mod; build(1,1,n); for(int i=1;i<=n;i++) { int num; cin>>num; modify(1,i,i,1,num); } int p;cin>>p; while(p--) { int t;cin>>t; if(t==1) { int a,b,c;cin>>a>>b>>c; modify(1,a,b,c,0); } else if(t==2) { int a,b,c;cin>>a>>b>>c; modify(1,a,b,1,c); } else { int a,b;cin>>a>>b; cout<<query(1,a,b)<<endl; } } }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· winform 绘制太阳,地球,月球 运作规律
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人