理解 ”加法+乘法“ 线段树 - Luogu P3373 【模板】线段树 2
step 0 - 引子
在看了众多题解后,我仍然没明白这样一个问题:为什么会想到延迟标记下传要分加法和乘法,而做乘法时还要把加法的 tag 一并乘了?如何想到这种联系?
显然,在尝试一个 lazytag 不行之后,我们肯定能想到同时维护两个延迟标记。最开始是天真的,尝试把加法和乘法分离来写——结果发现优先度的问题我似乎并不能解决:该先做哪个运算?本来想得到
经过几番思考,看了几轮题解,我似乎有点思路了:其实我们就是要避开优先级!也就是说在运算的过程中先做哪个运算与原数无关,原数在运算结束时才起效。这样,我们才能保证以
假设我们的原数等于
step 1 - 只加 与 只乘
我们先讨论只有加法与只有乘法两种情况,以便理解优先度的概念。
-
假设
。通过模拟一系列程序运算的过程,我们知道答案是 , 表示的是第 次加的数 。但是由于括号的原因,有个先后顺序,得先算括号内的,再算括号外的,因此计算每次都要涉及原数 。想要避开优先度?我们可以通过多年的数学经验,由结合律得 。这样一来,由于加法的交换律,我们知道运算的先后与 无关, 只在运算末尾起效了! -
同样的,假设
,由于乘法也有结合律和交换律,满足 ,运算的先后也与 无关。于是避开了优先度。
step 2 - 又加又乘
若以
由 step 1 情况 1 得:
设
对式子
由数学归纳法得:
扩展一下,若把
step 3 - 线段树
回到线段树。定义函数
我们发现若节点
下传标记,因为
step 4 - 总结(思维方式)
若线段树节点的真实值与储存值满足多项式关系,且每次运算不会使改多项式次数变化,则可以下传延迟标记。(不过我目前没有想到有没有高于一次的多项式关系的运算还不升次或降次……)
step 5 - 上代码!
code:
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e5+1;
int n,m,P,A[N],tree[N*4];
int markA[N*4],markM[N*4];
void dfs(int l=1,int r=n,int root=1){
markM[root]=1;
if(l==r){tree[root]=A[l]%P;return ;}
int mid=(l+r)>>1;
dfs(l,mid,root<<1);
dfs(mid+1,r,root<<1|1);
tree[root]=(tree[root<<1]+tree[root<<1|1])%P;
}
void pushdown(int p,int l,int r){
int len=r-l+1;
tree[p<<1]=(tree[p<<1]*markM[p]+markA[p]*(len-len/2))%P;
tree[p<<1|1]=(tree[p<<1|1]*markM[p]+markA[p]*(len/2))%P;
markM[p<<1]=markM[p<<1]*markM[p]%P;
markM[p<<1|1]=markM[p<<1|1]*markM[p]%P;
markA[p<<1]=(markA[p<<1]*markM[p]+markA[p])%P;
markA[p<<1|1]=(markA[p<<1|1]*markM[p]+markA[p])%P;
markM[p]=1;
markA[p]=0;
}
inline void UpdA(int d,int L,int R,int l=1,int r=n,int p=1){
if(L>r||l>R)return ;
if(L<=l&&r<=R){
tree[p]=(tree[p]+d*(r-l+1))%P;
if(r>l)markA[p]=(markA[p]+d)%P;
return ;
}int mid=(l+r)>>1;
pushdown(p,l,r);
UpdA(d,L,R,mid+1,r,p<<1|1);
UpdA(d,L,R,l,mid,p<<1);
tree[p]=(tree[p<<1]+tree[p<<1|1])%P;
}
void UpdM(int d,int L,int R,int l=1,int r=n,int p=1){
if(L>r||l>R)return ;
if(L<=l&&r<=R){
tree[p]=tree[p]*d%P;
if(r>l){
markM[p]=markM[p]*d%P;
markA[p]=markA[p]*d%P;
}return ;
}int mid=(l+r)>>1;
pushdown(p,l,r);
UpdM(d,L,R,mid+1,r,p<<1|1);
UpdM(d,L,R,l,mid,p<<1);
tree[p]=(tree[p<<1]+tree[p<<1|1])%P;
}
int query(int L,int R,int l=1,int r=n,int p=1){
if(L>r||l>R)return 0;
if(L<=l&&r<=R)return tree[p]%P;
int mid=(l+r)>>1;
pushdown(p,l,r);
return (query(L,R,mid+1,r,p<<1|1)+query(L,R,l,mid,p<<1))%P;
}
signed main(){
scanf("%lld%lld%lld",&n,&m,&P);
for(int i=1;i<=n;i++)scanf("%lld",A+i);
dfs();
for(int i=1;i<=m;i++){
int o,x,y,k;
scanf("%lld%lld%lld",&o,&x,&y);
if(o!=3){
scanf("%lld",&k);
if(o==2)UpdA(k,x,y);
else UpdM(k,x,y);
}else printf("%lld\n",query(x,y));
}
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律