线段树模板(维护加法和乘法 c++实现)
本章主要总结线段树的模板,有关于lazytag标记与线段树的维护乘法和加法的模板
我们用tag这个结构体分别表示add和mul标记,tree存储区间和
int p; //余数 const int N=1e5+10; int tree[N<<2]; //区间和 struct tag { int add,mul; }tag[N<<2]
push_down函数
对于孩子节点的标记,我们统称为孩子节点的… 例如孩子节点的乘法标记 …
对于父亲节点的标记,我们省略,例如乘法标记,加法标记,表示父亲节点的。
- 处理加法标记:孩子节点加法标记 * 乘法标记 + 加法标记
- 处理乘法标记:孩子节点乘法标记 * 乘法标记
- 处理区间和:孩子节点区间和 * 乘法标记 + 加法标记 * 区间长度
inline void settag(int i,int pl,int pr,int multag,int addtag) { tag[i].mul=(tag[i].mul*multag)%p; //乘法标记 tag[i].add=(tag[i].add*multag%p+addtag)%p; //加法标记 tree[i]=(tree[i]*multag%p+addtag*(pr-pl+1)%p)%p;//区间和 }
同时对于push_down函数,我们也要进行相应的处理,注意加法标记清零,乘法标记置1
inline void push_down(int i,int pl,int pr) { //加法标记: int mid=(pl+pr)>>1; settag(ls(i),pl,mid,tag[i].mul,tag[i].add); //处理左孩子 settag(rs(i),mid+1,pr,tag[i].mul,tag[i].add);//处理右孩子 //原始标记清除 tag[i].add=0; tag[i].mul=1; return; }
区间更新
区间的更新涉及update函数,同时对于加法与乘法的update函数它们添加标记的方式也有不同。
对于维护加法的添加标记:区间内的每个数都加上k
- 加法标记加上k
- 区间和加上 k*区间长度
void addtag(int i,int pl,int pr) { tag[i].add=(tag[i].add+k)%mod; tree[i]=(tree[i]+k*(pr-pl+1)%mod)%mod; }
对于维护乘法的添加标记:区间的每个数都乘以k
- 乘法标记乘k
- 加法标记乘k
- 区间和乘k
void multag(int i,int pl,int pr) { tag[i].mul=tag[i].mul*k%p; tag[i].add=tag[i].add*k%p; tree[i]=tree[i]*k%p; }
update函数其他写法与正常的update函数一致:
//加法更新线段树 void update_add(int i,int pl,int pr,int L,int R,int k) { if (L<=pl && pr<=R) { //子区间被完全覆盖时,这是个待修改的区间,添加标记 tree[i]=(tree[i]+k*(pr-pl+1)%p)%p; //区间和+k*区间长度 tag[i].add=(tag[i].add+k)%p; //加法标记+k return; } push_down(i,pl,pr); //标记下移 int mid=(pl+pr)>>1; if (L<=mid) update_add(ls(i),pl,mid,L,R,k); if (R>mid) update_add(rs(i),mid+1,pr,L,R,k); push_up(i); //标记上移 } //乘法更新线段树 void update_mul(int i,int pl,int pr,int L,int R,int k) { if (L<=pl && pr<=R) { //子区间完全被覆盖,标记乘法 tag[i].mul=tag[i].mul*k%p; tag[i].add=tag[i].add*k%p; tree[i]=tree[i]*k%p; return; } push_down(i,pl,pr); int mid=(pl+pr)>>1; if (L<=mid) update_mul(ls(i),pl,mid,L,R,k); if (R>mid) update_mul(rs(i),mid+1,pr,L,R,k); push_up(i); }
区间查询
我们的区间查询函数与普通的查询函数基本一致:
//查询区间和 int query(int i,int pl,int pr,int L,int R) { if (L<=pl && pr<=R) { return tree[i]; } int res=0; push_down(i,pl,pr); //下移标记 int mid=(pl+pr)>>1; if (L<=mid) res+=query(ls(i),pl,mid,L,R); if (R>mid) res+=query(rs(i),mid+1,pr,L,R); return res%p; }
需要注意的地方
- 在处理区间乘法的时候,我们要注意初始化乘法标记为1,加法标记为0
- 我们最好在处理乘法的时候始终使用 long long 的数据类型
- 注意数组区间开四倍
- 不要忘记 取余,对于大数的运算多取余肯定没错
模板代码
另外附带在OJ模式下我常用的模板,可以参考一下
#include <bits/stdc++.h> using namespace std; using LL = long long; using DB = double; using PI = pair<int, int>; using PL = pair<LL, LL>; template<typename T> using v = vector<T>; constexpr auto INF = 0X3F3F3F3F; template<typename T1,typename T2> using umap = unordered_map<T1, T2>; #define ic std::ios::sync_with_stdio(false);std::cin.tie(nullptr) template <typename ConTainermap> void dbgumap(ConTainermap c); //output umap #if 1 #define int LL #endif inline int read(); //fast input inline void write(int x); //fast output //TODO: Write code here int n,m,p; const int N=1e5+10; int nums[N<<1]; int tree[N<<2]; //存储和 struct tag { int add,mul; //乘法和加法标记 }tag[N<<2]; inline int ls(int i) { return i<<1; } inline int rs(int i) { return i<<1|1; } inline void push_up(int i) { //求区间和 tree[i]=(tree[ls(i)]+tree[rs(i)])%p; } inline void settag(int i,int pl,int pr,int multag,int addtag) { tag[i].mul=(tag[i].mul*multag)%p; //乘法标记 tag[i].add=(tag[i].add*multag%p+addtag)%p; //加法标记 tree[i]=(tree[i]*multag%p+addtag*(pr-pl+1)%p)%p;//区间和 } inline void push_down(int i,int pl,int pr) { //加法标记: int mid=(pl+pr)>>1; settag(ls(i),pl,mid,tag[i].mul,tag[i].add); settag(rs(i),mid+1,pr,tag[i].mul,tag[i].add); //原始标记清除 tag[i].add=0; tag[i].mul=1; return; } //创建线段树 void build(int i,int pl,int pr) { if (pl==pr) { tree[i]=nums[pl]; return; } int mid=(pl+pr)>>1; build(ls(i),pl,mid); //递归左孩子 build(rs(i),mid+1,pr); //递归右孩子 push_up(i); //自底向上传递到父节点 } //加法更新线段树 void update_add(int i,int pl,int pr,int L,int R,int k) { if (L<=pl && pr<=R) { //子区间被完全覆盖时,这是个待修改的区间,添加标记 tree[i]=(tree[i]+k*(pr-pl+1)%p)%p; //区间和+k*区间长度 tag[i].add=(tag[i].add+k)%p; //加法标记+k return; } push_down(i,pl,pr); //标记下移 int mid=(pl+pr)>>1; if (L<=mid) update_add(ls(i),pl,mid,L,R,k); if (R>mid) update_add(rs(i),mid+1,pr,L,R,k); push_up(i); //标记上移 } //乘法更新线段树 void update_mul(int i,int pl,int pr,int L,int R,int k) { if (L<=pl && pr<=R) { //子区间完全被覆盖,标记乘法 tag[i].mul=tag[i].mul*k%p; tag[i].add=tag[i].add*k%p; tree[i]=tree[i]*k%p; return; } push_down(i,pl,pr); int mid=(pl+pr)>>1; if (L<=mid) update_mul(ls(i),pl,mid,L,R,k); if (R>mid) update_mul(rs(i),mid+1,pr,L,R,k); push_up(i); } //查询区间和 int query(int i,int pl,int pr,int L,int R) { if (L<=pl && pr<=R) { return tree[i]; } int res=0; push_down(i,pl,pr); //下移标记 int mid=(pl+pr)>>1; if (L<=mid) res+=query(ls(i),pl,mid,L,R); if (R>mid) res+=query(rs(i),mid+1,pr,L,R); return res%p; } signed main() { cin>>n>>m>>p; for (int i=1;i<=n;i++) { tag[i].mul=1; } for(int i=1;i<=n;i++) { scanf("%lld",&nums[i]); } build(1,1,n); for (int i=1;i<=m;i++) { int w,l,r,k; scanf("%lld",&w); if (w==1) { //区间每个数乘k scanf("%lld%lld%lld",&l,&r,&k); update_mul(1,1,n,l,r,k); } else if (w==2) { //区间每个数加k scanf("%lld%lld%lld",&l,&r,&k); update_add(1,1,n,l,r,k); } else if (w==3) { //查询区间和 scanf("%lld%lld",&l,&r); printf("%lld\n",query(1,1,n,l,r)%p); } } return 0; } template <typename ConTainermap> void dbgumap(ConTainermap c) { for (auto& x:c) { cout<<"key:"<<x.first<<" val:"<<x.second<<endl; } } inline int read() { int x = 0, w = 1; char ch = 0; while (ch < '0' || ch > '9') { if (ch == '-') w = -1; ch = getchar(); } while (ch >= '0' && ch <= '9') { x = x * 10 + (ch - '0'); ch = getchar(); } return x * w; } inline void output(int x) { static int sta[35]; int top = 0; do { sta[top++] = x % 10, x /= 10; } while (x); while (top) putchar(sta[--top] + 48); }
本文来自博客园,作者:hugeYlh,转载请注明原文链接:https://www.cnblogs.com/helloylh/p/17209616.html
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· 写一个简单的SQL生成工具
· AI 智能体引爆开源社区「GitHub 热点速览」
· C#/.NET/.NET Core技术前沿周刊 | 第 29 期(2025年3.1-3.9)