线段树(1~2)模板
线段树(1~2)模板
线段树1:
时间复杂度:\(O(n+m\log n)\)
#include<bits/stdc++.h>
#define lc p<<1 //左儿子编号
#define rc p<<1|1 //右儿子编号
using namespace std;
const int N=1e5+5;
int n,m,op,x,y;
long long a[N],k;
struct node{
int l,r;//节点代表的区间[l,r]
long long sum,tag;//sum:区间和;tag(懒标记):该区间整体需要加上的值
}tr[4*N];
void pushup(int p){//用左右儿子的信息更新当前节点
tr[p].sum=tr[lc].sum+tr[rc].sum;
}
void pushdown(int p){//将懒标记下传给左右儿子,并清空当前标记
tr[lc].sum+=tr[p].tag*(tr[lc].r-tr[lc].l+1);//左儿子区间长度=tr[lc].r-tr[lc].l+1
tr[rc].sum+=tr[p].tag*(tr[rc].r-tr[rc].l+1);//右儿子区间长度同上
//懒标记累加
tr[lc].tag+=tr[p].tag;
tr[rc].tag+=tr[p].tag;
tr[p].tag=0;//清空当前节点的懒标记
}
void build(int p,int l,int r){//建树:递归构建线段树
tr[p]={l,r};//初始化节点区间,sum和tag会被自动设为0
if(l==r){//叶子节点
tr[p].sum=a[l];
return ;
}
int mid=(l+r)>>1;
build(lc,l,mid);
build(rc,mid+1,r);
pushup(p);//回溯时合并区间和
}
void update(int p,int l,int r,long long k){//区间修改
if(l<=tr[p].l&&tr[p].r<=r){//当前节点区间完全被查询区间覆盖
tr[p].sum+=k*(tr[p].r-tr[p].l+1);
tr[p].tag+=k;//打上懒标记
return ;
}
pushdown(p);//需要继续递归,先下传标记
int mid=(tr[p].l+tr[p].r)>>1;
if(mid>=l) update(lc,l,r,k);//左儿子与查询区间有交集
if(mid<r) update(rc,l,r,k);//右儿子与查询区间有交集
pushup(p);//更新当前节点
}
long long query(int p,int l,int r){//区间查询
if(l<=tr[p].l&&tr[p].r<=r){//完全覆盖直接返回
return tr[p].sum;
}
long long ans=0;
pushdown(p);//下传懒标记
int mid=(tr[p].l+tr[p].r)>>1;
if(mid>=l) ans+=query(lc,l,r);//左部分
if(mid<r) ans+=query(rc,l,r);//右部分
return ans;
}
int main(){
ios::sync_with_stdio(0);cin.tie(0);
cin>>n>>m;
for(int i=1;i<=n;i++) cin>>a[i];
build(1,1,n);//建树!!!
while(m--){
cin>>op>>x>>y;
if(op==1){
cin>>k;
update(1,x,y,k);//区间加
}else{
cout<<query(1,x,y)<<'\n';//区间和
}
}
return 0;
}
关于线段树用静态数组存要开 $4$ 倍空间的简略证明
证明:
首先我们知道,线段树的数组存储中,最大的下标出现在某个叶子节点上。树的高度为 \(H=\left \lceil \log_{2}{n} \right \rceil\).
对于完全二叉树(包括不完全但按数组存储),最后一层的节点编号最大可能达到:\(2^{H+1}-1\).
而 \(H=\left \lceil \log_{2}{n} \right \rceil\),所以:\(2^{H+1}-1 \le 2^{\log_{2}{n}+2 }-1=4n-1\).
因此最大下标不超过 \(4n-1\).所以开 \(4\) 倍空间.
证毕.
线段树1.5:
1.动态开点:
时间复杂度:\(O(m\log n)\)
#include<bits/stdc++.h>
using namespace std;
using ULL=unsigned long long;//此题要用ULL,平常可以long long,防止负数的问题
const int N=1e5+5;
ULL n,m,sum[N*100],id,lc[N*100],rc[N*100],tag[N*100],rt;
ULL qq(ULL l,ULL r){//计算原数组中区间[l,r]的和,初始a_i=i
return (l+r)*(r-l+1)/2;
}
void pushup(ULL p){//用左右儿子的和更新当前节点
sum[p]=sum[lc[p]]+sum[rc[p]];
}
void pushdown(ULL p,ULL l,ULL r,ULL mid){//下传懒标记
if(tag[p]){
if(!lc[p]) lc[p]=++id;//动态开点
if(!rc[p]) rc[p]=++id;
sum[lc[p]]+=(mid-l+1)*tag[p];
sum[rc[p]]+=(r-mid)*tag[p];
tag[lc[p]]+=tag[p];
tag[rc[p]]+=tag[p];
tag[p]=0;
}
}
//引用传递保证了动态开点时节点编号的写回,是动态开点线段树能够正确建树的关键
void update(ULL &p,ULL l,ULL r,ULL ql,ULL qr,ULL k){//区间加法:将[ql,qr]内每个数加k
if(!p) p=++id;//节点不存在则创建
if(ql<=l&&r<=qr){//当前区间完全覆盖
sum[p]+=(r-l+1)*k;
tag[p]+=k;return ;
}
ULL mid=(l+r)>>1;
pushdown(p,l,r,mid);//下传标记
if(ql<=mid) update(lc[p],l,mid,ql,qr,k);
if(qr>mid) update(rc[p],mid+1,r,ql,qr,k);
pushup(p);
}
ULL query(ULL p,ULL l,ULL r,ULL ql,ULL qr){//区间查询:[ql,qr]的和(动态开点部分)
if(!p) return 0;
if(ql<=l&&r<=qr) return sum[p];
ULL mid=(l+r)>>1,ans=0;
pushdown(p,l,r,mid);
if(ql<=mid) ans+=query(lc[p],l,mid,ql,qr);
if(qr>mid) ans+=query(rc[p],mid+1,r,ql,qr);
return ans;
}
int main(){
ios::sync_with_stdio(0);cin.tie(0);
cin>>n>>m;
while(m--){
ULL op,l,r,k;cin>>op>>l>>r;
if(op==1){
cin>>k;
update(rt,1,n,l,r,k);//动态开点线段树根节点初始为0
}else{
cout<<query(rt,1,n,l,r)+qq(l,r)<<'\n';//查询结果=动态开点部分维护的增量+原始序列区间和
}
}
return 0;
}
2.标记永久化:
时间复杂度:\(O(m\log n)\)
#include<bits/stdc++.h>
using namespace std;
using ULL=unsigned long long;
const int N=1e5+5;
ULL n,m,sum[N*100],id,lc[N*100],rc[N*100],tag[N*100],rt;
ULL qq(ULL l,ULL r){//计算原数组中区间[l,r]的和,初始a_i=i
return (l+r)*(r-l+1)/2;
}
void update(ULL &p,ULL l,ULL r,ULL ql,ULL qr,ULL k){//区间加法:将[ql,qr]内每个数加k,采用标记永久化
if(!p) p=++id;//动态开点
//即使当前节点未被完全覆盖,也要先累加与查询区间的交集贡献,保证sum[p]正确
sum[p]+=(min(r,qr)-max(l,ql)+1)*k;//交集长度*k
if(ql<=l&&r<=qr){//完全覆盖:只需要更新tag,不再递归
tag[p]+=k;return ;
}
ULL mid=(l+r)>>1;
if(ql<=mid) update(lc[p],l,mid,ql,qr,k);
if(qr>mid) update(rc[p],mid+1,r,ql,qr,k);
}
ULL query(ULL p,ULL l,ULL r,ULL ql,ULL qr,ULL tk){//区间查询:返回[ql,qr]内的增量总和;tk表示祖先链路上的懒标记累积和
if(!p) return (min(r,qr)-max(l,ql)+1)*tk;//空节点:贡献等于区间长度×祖先标记累积
if(ql<=l&&r<=qr) return sum[p]+(r-l+1)*tk;//完全覆盖:直接返回节点sum+祖先标记贡献
ULL mid=(l+r)>>1,ans=0;
//递归子区间,并带上当前节点的tag
if(ql<=mid) ans+=query(lc[p],l,mid,ql,qr,tk+tag[p]);
if(qr>mid) ans+=query(rc[p],mid+1,r,ql,qr,tk+tag[p]);
return ans;
}
int main(){
ios::sync_with_stdio(0);cin.tie(0);
cin>>n>>m;
while(m--){
ULL op,l,r,k;cin>>op>>l>>r;
if(op==1){
cin>>k;
update(rt,1,n,l,r,k);
}else{
cout<<query(rt,1,n,l,r,0)+qq(l,r)<<'\n';//查询结果=动态开点部分维护的增量+原始序列区间和
}
}
return 0;
}
线段树2:
时间复杂度:\(O(n+m\log n)\)
#include<bits/stdc++.h>
#define lc p<<1
#define rc p<<1|1
using namespace std;
const int N=1e5+5;
struct node{
int l,r;
long long sum,tag,mul;//sum:区间和,tag:加法懒标记,mul:乘法懒标记
}tr[4*N];
int n,m,mod,w[N];
void pushup(int p){
tr[p].sum=(tr[lc].sum+tr[rc].sum)%mod;
}
void pushdown(int p){//下传懒标记
//注意下传顺序:先乘后加,因为标记格式是sum*mul+add
tr[lc].sum=(tr[lc].sum*tr[p].mul+tr[p].tag*(tr[lc].r-tr[lc].l+1))%mod;
tr[rc].sum=(tr[rc].sum*tr[p].mul+tr[p].tag*(tr[rc].r-tr[rc].l+1))%mod;
//加法标记更新:原来的加标记需要乘以父节点的乘标记,再加上父节点加标记
tr[lc].tag=(tr[p].tag+tr[lc].tag*tr[p].mul)%mod;
tr[rc].tag=(tr[p].tag+tr[rc].tag*tr[p].mul)%mod;
//乘法标记更新
tr[lc].mul=(tr[lc].mul*tr[p].mul)%mod;
tr[rc].mul=(tr[rc].mul*tr[p].mul)%mod;
tr[p].tag=0;
tr[p].mul=1;
}
void build(int p,int x,int y){
tr[p]={x,y,w[x]%mod,0,1};//叶子或节点初始化
if(x==y) return ;
int mid=(x+y)>>1;
build(lc,x,mid);
build(rc,mid+1,y);
pushup(p);
}
void update(int p,int x,int y,int k){//区间加法
if(tr[p].l>=x&&tr[p].r<=y){//完全覆盖
tr[p].sum+=k*(tr[p].r-tr[p].l+1);
tr[p].tag+=k;
return ;
}
pushdown(p);
int mid=(tr[p].r+tr[p].l)>>1;
if(mid>=x) update(lc,x,y,k);
if(mid<y) update(rc,x,y,k);
pushup(p);
}
void updates(int p,int x,int y,int k){//区间乘法
if(tr[p].l>=x&&tr[p].r<=y){//完全覆盖
tr[p].sum=(k*tr[p].sum)%mod;
tr[p].tag=(tr[p].tag*k)%mod;
tr[p].mul=(tr[p].mul*k)%mod;
return ;
}
pushdown(p);
int mid=(tr[p].r+tr[p].l)>>1;
if(mid>=x) updates(lc,x,y,k);
if(mid<y) updates(rc,x,y,k);
pushup(p);
}
long long query(int p,int x,int y){//区间查询
if(tr[p].l>=x&&tr[p].r<=y){//完全覆盖
return tr[p].sum%mod;
}
long long ans=0;
pushdown(p);
int mid=(tr[p].r+tr[p].l)>>1;
if(mid>=x) ans+=query(lc,x,y)%mod;
if(mid<y) ans+=query(rc,x,y)%mod;
return ans%mod;
}
int main(){
cin.tie(0)->ios::sync_with_stdio(0);
cin>>n>>m>>mod;
for(int i=1;i<=n;i++) cin>>w[i];
build(1,1,n);
while(m--){
int op,x,y,k;cin>>op>>x>>y;
if(op==1){
cin>>k;
updates(1,x,y,k);//区间乘
}else if(op==2){
cin>>k;
update(1,x,y,k);//区间加
}else{
cout<<query(1,x,y)<<'\n';//区间求和
}
}
return 0;
}

浙公网安备 33010602011771号