「模板」线段树静态开点(单点+区间修改)动态开点
Posted locaetric
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了「模板」线段树静态开点(单点+区间修改)动态开点相关的知识,希望对你有一定的参考价值。
相关讲解资料:
树状数组:https://blog.csdn.net/qq_34374664/article/details/52787481 (线段树预备)
线段树讲解:
代码:
完整注释模板一张,参(chao)考(xi)楼上的博客
#include<iostream> #include<cstdio> #include<cstdlib> #include<cstring> #include<cmath> #include<queue> #include<algorithm> using namespace std; typedef long long ll; #define mid (l+r)/2 #define lson (pos<<1) #define rson ((pos<<1)|1) #define maxn 100007 //元素个数 ll n,m; ll root=1; ll arr[maxn]; ll Lazy[maxn<<2];//区间增加的lazy标记 /*其目的是: 为防止修改区间总结点对每个子节点都要进行修改,导致复杂度爆炸 暂时记录一下这个区间总结点的所有子树都“待修改” 如果用到下面的子节点就修改,下推lazy标志,用不到就不管 以此来减少复杂度 */ ll sum[maxn<<2];//线段树求和最多分成4个子区间 void PushUp(long long pos)//暂时写成求和函数,可以自由变换 { sum[pos]=sum[lson]+sum[rson]; //用数组表示二叉树:假设某个节点的编号为v,那么它的左子节点编号为2*v,右子节点编号为2*v+1,规定根节点为1 //通常2*v写成v<<1 , 2*v+1写成v<<1|1; } void PushDown(long long pos,long long l,long long r)//区间查询用 { //l,r为左子树,右子树的数字区间 if(Lazy[pos]) { //修改子节点的增加数 Lazy[lson]+=Lazy[pos]; Lazy[rson]+=Lazy[pos]; //修改子节点区间的sum sum[lson]+=Lazy[pos]*(mid-l+1); sum[rson]+=Lazy[pos]*(r-(mid+1)+1); //清除本节点标记 Lazy[pos]=0; } } void Build(long long l,long long r,long long pos)//[l,r]表示当前节点区间,pos表示当前节点的实际存储位置 { if(l==r)//如果到达儿子节点,存储并返回 { sum[pos]=arr[l]; return; } Build(l,mid,pos<<1); Build(mid+1,r,pos<<1|1); PushUp(pos); } void UpPoint(long long pos,long long l,long long r,long long L,long long C)//对单点修改 { //L表示要修改的点编号,[l,r]表示当前区间,pos是当前节点编号; if(l==r)//到达儿子节点之后就修改 { sum[pos]+=C; return; } //根据条件判断往左子树调用还是往右 if(L<=mid) UpPoint(lson,l,mid,L,C); else UpPoint(rson,mid+1,r,L,C); PushUp(pos);//子节点更新之后本节点也需要更新; } void UpZone(long long pos,long long l,long long r,long long L,long long R,long long C)//对整个区间进行修改 { //L,R表示操作区间 , l,r表示当前节点区间 , pos表示当前节点编号 if(L<=l && R>=r)//节点区间在操作区间之内,直接返回 { sum[pos]+=C*(r-l+1);//这个点需要加上区间长度*C Lazy[pos]+=C;//用Lazy标记,表示本区间的Sum正确,子区间的Sum仍需要根据Add调整 return; } PushDown(pos,l,r);//下推标记 if(L<=mid) UpZone(lson,l,mid,L,R,C); if(R>mid) UpZone(rson,mid+1,r,L,R,C); PushUp(pos); } ll Query(long long l,long long r,long long L,long long R,long long pos) { //L,R表示操作区间 , l,r表示当前节点区间 , pos表示当前节点编号 if(L<=l && R>=r)//节点区间在操作区间之内,直接返回 { return sum[pos]; } PushDown(pos,l,r);//下推标记,否则sum可能不正确 //统计答案 long long ans=0; if(L<=mid) ans+=Query(l,mid,L,R,lson); if(R>mid) ans+=Query(mid+1,r,L,R,rson); PushUp(pos); return ans; } int main() { cin>>n>>m; for(int i=1;i<=n;i++) { long long tmp; cin>>tmp; UpZone(root,1,n,i,i,tmp); } for(int j=1;j<=m;j++) { long long a,b,c,d; cin>>a; if(a==1) { cin>>b>>c>>d; UpZone(root,1,n,b,c,d); } else { cin>>b>>c; cout<<Query(1,n,b,c,root)<<endl; } } return 0; }
下面是动态开点的模板:
1. 不能define lson,rson,也不能用pos<<1和pos<<1|1,否则就失去了“动态开点”的意义
2. Get_Son和UpZone要&引用
3. 尽量开long long,也好调
#include<iostream> #include<cstdio> #include<cstdlib> #include<cstring> #include<cmath> #include<queue> #include<algorithm> using namespace std; typedef long long ll; //!!!!!!!!! //Get_Son和UpZone要&引用 //不能define lson,rson,也不能用pos<<1和pos<<1|1 //!!!!!!!!! #define mid (l+r)/2 #define maxn 1000007 //元素个数 ll n,m; ll root=1,cnt=1; ll lson[maxn],rson[maxn]; ll Lazy[maxn<<2];//区间增加的lazy标记 /*其目的是: 为防止修改区间总结点对每个子节点都要进行修改,导致复杂度爆炸 暂时记录一下这个区间总结点的所有子树都“待修改” 如果用到下面的子节点就修改,下推lazy标志,用不到就不管 以此来减少复杂度 */ ll sum[maxn<<2];//线段树求和最多分成4个子区间 ll Get_Son(long long &pos) { if(pos==0) pos=++cnt; return pos; } void PushUp(long long pos) { sum[pos]=sum[lson[pos]]+sum[rson[pos]]; //用数组表示二叉树:假设某个节点的编号为v,那么它的左子节点编号为2*v,右子节点编号为2*v+1,规定根节点为1 //通常2*v写成v<<1 , 2*v+1写成v<<1|1; } void PushDown(long long pos,long long l,long long r)//区间查询用 { //l,r为左子树,右子树的数字区间 // if(Lazy[pos]==0) return; // if(r-l<=1) return; // if(pos<<1!=0) // { // pos<<1=++cnt; // sum[pos<<1]+=(mid-l+1)*Lazy[pos]; // Lazy[pos<<1]+=Lazy[pos]; // } // if(rson[pos]!=0) // { // rson[pos]=++cnt; // sum[rson[pos]]+=(r-mid+1)*Lazy[pos]; // Lazy[rson[pos]]+=Lazy[pos]; // } sum[Get_Son(lson[pos])]+=(mid-l+1)*Lazy[pos]; sum[Get_Son(rson[pos])]+=(r-mid)*Lazy[pos]; Lazy[lson[pos]]+=Lazy[pos]; Lazy[rson[pos]]+=Lazy[pos]; Lazy[pos]=0; } void UpZone(long long &pos,long long l,long long r,long long L,long long R,long long C) { //L,R表示操作区间 , l,r表示当前节点区间 , pos表示当前节点编号 if(pos==0) pos=++cnt; if(Lazy[pos]!=0) PushDown(pos,l,r);//下推标记 if(L<=l && R>=r)//节点区间在操作区间之内,直接返回 { sum[pos]+=(r-l+1)*C;//这个点需要加上区间长度*C Lazy[pos]+=C;//用Lazy标记,表示本区间的Sum正确,子区间的Sum仍需要根据Lazy调整 return; } if(L<=mid) UpZone(lson[pos],l,mid,L,R,C); if(R>mid) UpZone(rson[pos],mid+1,r,L,R,C); PushUp(pos); } ll Query(long long pos,long long l,long long r,long long L,long long R) { //L,R表示操作区间 , l,r表示当前节点区间 , pos表示当前节点编号 if(pos==0) return 0; if(Lazy[pos]) PushDown(pos,l,r);//下推标记,否则sum可能不正确 if(L<=l && R>=r)//节点区间在操作区间之内,直接返回 { return sum[pos]; } //统计答案 long long ans=0; if(L<=mid) ans+=Query(lson[pos],l,mid,L,R); if(R>mid) ans+=Query(rson[pos],mid+1,r,L,R); PushUp(pos); return ans; } int main() { cin>>n>>m; for(int i=1;i<=n;i++) { int tmp; cin>>tmp; UpZone(root,1,n,i,i,tmp); } for(int j=1;j<=m;j++) { int a,b,c,d; cin>>a; if(a==1) { cin>>b>>c>>d; UpZone(root,1,n,b,c,d); } else { cin>>b>>c; cout<<Query(root,1,n,b,c)<<endl; } } return 0; }
以上是关于「模板」线段树静态开点(单点+区间修改)动态开点的主要内容,如果未能解决你的问题,请参考以下文章
CF915E Physical Education Lessons|动态开点线段树
#RMQ,动态开点线段树#CF803G Periodic RMQ Problem