线段树笔记
线段树是用于在区间上进行信息统计的二叉树。
线段树的性质
- 每个节点都代表一个区间。
- 有唯一的根节点,代表整体区间
- 每个夜间点代表长度为
的单位区间 - 出叶节点和根节点之外的内部节点
,取 ,左子节点为 ,右子节点为 。 - 除最后一层,线段树为完全二叉树,深度为
。 - 如果用数组来保存线段树,长度需要
,故会有空间上的浪费。
线段树的建立
对于区间
如果
否则,将区间分成左右两部分进行递归。取
void build(int p,int l,int r){ t[p].l=l,tree[p].r=r; if(l==r){ t[p].num=a[l]; return ; } int mid=l+(r-l)/2; build(p*2,l,mid); build(p*2+1,mid+1,r); t[p].dat=max(t[p*2].dat,t[p*2+1].dat); //注意对于不同的题目维护的值会不一样,在此处为维护区间最大值 return ; }
线段树的单点修改
线段树的单点修改遵循以下步骤:
假设修改第
从下往上,依次更新父节点的信息,维护线段树的性质。
void modify(int p,int k,int val){ int l=t[p].l,r=t[p].r; if(l==r){ t[p].dat=val; return ; } int mid=l+(r-l)/2; if(k<=mid) modify(p*2,k,val); else modify(p*2+1,k,val); t[p].dat=max(t[p*2].dat,t[p*2+1].dat); //此处维护区间最大值 return ; }
线段树的区间查询
对于一个区间
如果现在查到了区间
int query(int p,int L,int R){ int l=t[p].l,r=t[p].r; if(l>=L&&r<=R) return t[p].dat; int mid=l+(r-l)/2; int res=-(1<<30); if(L<=mid) val=max(res,query(p*2,L,R)); if(R>mid) val=max(res,query(p*2+1,L,R)); return res; }
线段树的区间修改
在区间修改时,如果某个节点
而且,如果
lazy tag
lazy tag 给
如果在后续指令中,需要从节点
- 检查标记状态,根据标记更新
的子节点。 - 给
的子节点更新标记状态。 - 删除
的标记状态。
这样做,区间修改的复杂度就降至
void pushdown(int p){ if(t[p].tag){ t[p*2].sum+=t[p].tag*(t[p*2].r-t[p*2].l+1); t[p*2+1].sum+=t[p].tag*(t[p*2+1].r-t[p*2+1].l+1); t[p*2].tag=t[p].tag; t[p*2+1].tag=t[p].tag; t[p].tag=0; } return ; }
在 modify 和 query 时都需要 pushdown。
例题 SP1716
本题在每个节点中,除了维护区间端点外,还要维护以下
区间和 sum,区间最大连续字段和 ans,靠左端的最大连续字段和 lmax,靠右端的最大连续字段和 rmax。
此外需要在 build 和 modify 函数中从下往上传递:
t[p].sum=t[p*2].sum+t[p*2+1].sum; t[p].lmax=max(t[p*2].lmax,t[p*2].sum+t[p*2+1].lmax); t[p].rmax=max(t[p*2+1].rmax,t[p*2+1].sum+t[p*2].rmax); t[p].ans=max(t[p*2].ans,max(t[p*2+1].ans,t[p*2].rmax+t[p*2+1].lmax));
对于叶子节点,sum,lmax,rmax,ans 均为
#include<bits/stdc++.h> using namespace std; typedef long long ll; const ll N=500009; const ll INF=0x3f3f3f3f3f3f3f3f; struct segment{ ll l,r,s,bstL,bstR,bst; }tr[N*4]; ll n,m,x[N]; void pushup(ll p){ ll ls=p*2,rs=p*2+1; tr[p].l=tr[ls].l; tr[p].r=tr[rs].r; tr[p].s=tr[ls].s+tr[rs].s; tr[p].bstL=max(tr[ls].bstL,tr[ls].s+tr[rs].bstL); tr[p].bstR=max(tr[rs].bstR,tr[rs].s+tr[ls].bstR); tr[p].bst=max(tr[ls].bst,max(tr[rs].bst,tr[ls].bstR+tr[rs].bstL)); } void build(ll p,ll l,ll r){ if(l==r){ tr[p]=((segment){l,r,x[l],x[l],x[r],x[l]}); return ; } ll mid=l+(r-l)/2; build(p*2,l,mid); build(p*2+1,mid+1,r); pushup(p); } ll queryL(ll p,ll l){ ll ls=p*2,rs=p*2+1; if(tr[p].r<l) return -INF; if(tr[p].l==tr[p].r) return tr[p].bst; if(l>tr[ls].r) return queryL(rs,l); return max(tr[rs].bstR,tr[rs].s+queryL(ls,l)); } ll queryR(ll p,ll r){ ll ls=p*2,rs=p*2+1; if(tr[p].l>r) return -INF; if(tr[p].l==tr[p].r) return tr[p].bst; if(r<tr[rs].l) return queryR(ls,r); return max(tr[ls].bstL,tr[ls].s+queryR(rs,r)); } ll query(ll p,ll l,ll r){ ll ls=p*2,rs=p*2+1; if(r<tr[p].l||l>tr[p].r) return -INF; if(l<=tr[p].l&&r>=tr[p].r) return tr[p].bst; return max(queryL(ls,l)+queryR(rs,r),max(query(ls,l,r),query(rs,l,r))); } void modify(ll p,ll i,ll e){ if(tr[p].l==tr[p].r){ if(tr[p].l==i) x[tr[p].l]=tr[p].bst=tr[p].bstL=tr[p].bstR=tr[p].s=e; return ; } if(tr[p*2].r>=i) modify(p*2,i,e); if(tr[p*2+1].l<=i) modify(p*2+1,i,e); pushup(p); } int main(){ ios::sync_with_stdio(false); cin>>n; for(int i=1;i<=n;i++) cin>>x[i]; build(1,1,n); cin>>m; for(int i=1;i<=m;i++){ ll op,l,r; cin>>op>>l>>r; if(op==1){ if(l>r) swap(l,r); cout<<query(1,l,r)<<endl; } else modify(1,l,r); } return 0; }
例题 P3373
本题维护两个 tag:乘法 tag 和加法 tag。但要注意运算的优先级,在更新加法 tag 之前需要把乘法 tag 先 pushdown。
#include<bits/stdc++.h> using namespace std; typedef long long ll; struct tree{ ll sum,tag1,tag2; }t[400009]; ll n,m,p,a[100009]; void build(ll now,ll l,ll r){ t[now].tag1=1; t[now].tag2=0; if(l==r){ t[now].sum=a[l]; return ; } ll mid=l+(r-l)/2; build(now*2,l,mid); build(now*2+1,mid+1,r); t[now].sum=(t[now*2].sum+t[now*2+1].sum)%p; return ; } void pushdown(ll now,ll l,ll r){ ll mid=l+(r-l)/2; t[now*2].sum=(t[now*2].sum*t[now].tag1+t[now].tag2*(mid-l+1))%p; t[now*2+1].sum=(t[now*2+1].sum*t[now].tag1+t[now].tag2*(r-mid))%p; t[now*2].tag1=(t[now*2].tag1*t[now].tag1)%p; t[now*2+1].tag1=(t[now*2+1].tag1*t[now].tag1)%p; t[now*2].tag2=(t[now*2].tag2*t[now].tag1+t[now].tag2)%p; t[now*2+1].tag2=(t[now*2+1].tag2*t[now].tag1+t[now].tag2)%p; t[now].tag1=1; t[now].tag2=0; return ; } void modify1(ll now,ll L,ll R,ll l,ll r,ll k){ if(r<L||l>R) return ; if(L<=l&&R>=r){ t[now].sum=(t[now].sum*k)%p; t[now].tag1=(t[now].tag1*k)%p; t[now].tag2=(t[now].tag2*k)%p; return ; } pushdown(now,l,r); ll mid=l+(r-l)/2; if(mid>=L) modify1(now*2,L,R,l,mid,k); if(mid+1<=R) modify1(now*2+1,L,R,mid+1,r,k); t[now].sum=(t[now*2].sum+t[now*2+1].sum)%p; return ; } void modify2(ll now,ll L,ll R,ll l,ll r,ll k){ if(r<L||l>R) return ; if(L<=l&&R>=r){ t[now].sum=(t[now].sum+(r-l+1)*k)%p; t[now].tag2=(t[now].tag2+k)%p; return ; } pushdown(now,l,r); ll mid=l+(r-l)/2; if(mid>=L) modify2(now*2,L,R,l,mid,k); if(mid+1<=R) modify2(now*2+1,L,R,mid+1,r,k); t[now].sum=(t[now*2].sum+t[now*2+1].sum)%p; return ; } ll query(ll now,ll L,ll R,ll l,ll r){ if(r<L||l>R) return 0; if(L<=l&&R>=r) return t[now].sum; pushdown(now,l,r); ll mid=l+(r-l)/2; ll res=0; if(mid>=L) res=(res+query(now*2,L,R,l,mid))%p; if(mid+1<=R) res=(res+query(now*2+1,L,R,mid+1,r))%p; return res; } int main(){ cin>>n>>m>>p; for(ll i=1;i<=n;i++) cin>>a[i]; build(1,1,n); for(ll i=1;i<=m;i++){ ll op,x,y,k; cin>>op; if(op==1){ cin>>x>>y>>k; modify1(1,x,y,1,n,k); } else if(op==2){ cin>>x>>y>>k; modify2(1,x,y,1,n,k); } else if(op==3){ cin>>x>>y; cout<<query(1,x,y,1,n)<<endl; } } return 0; }
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 在鹅厂做java开发是什么体验
· 百万级群聊的设计实践
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战
· 永远不要相信用户的输入:从 SQL 注入攻防看输入验证的重要性
· 全网最简单!3分钟用满血DeepSeek R1开发一款AI智能客服,零代码轻松接入微信、公众号、小程