线段树笔记

线段树是用于在区间上进行信息统计的二叉树。

线段树的性质

  1. 每个节点都代表一个区间。
  2. 有唯一的根节点,代表整体区间
  3. 每个夜间点代表长度为 1 的单位区间
  4. 出叶节点和根节点之外的内部节点 [l,r],取 mid=1+r2,左子节点为 [l,mid],右子节点为 [mid+1,r]
  5. 除最后一层,线段树为完全二叉树,深度为 logn
  6. 如果用数组来保存线段树,长度需要 4n,故会有空间上的浪费。

线段树的建立

对于区间 [p,l,r]p 表示节点编号,l 为区间左端点,r 为区间右端点。

如果 l=r,说明已经递归到了叶子节点,直接把对应的区间值放进去即可,然后返回。

否则,将区间分成左右两部分进行递归。取 mid=1+r2,左子节点为 [l,mid],右子节点为 [mid+1,r]。在左右子节点都完成递归之后,按照题意对该节点的数值进行维护,然后返回。

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 ;
}

线段树的单点修改

线段树的单点修改遵循以下步骤:

假设修改第 x 个节点,从根节点出发,递归找到区间 [x,x] 的节点,更新值。

从下往上,依次更新父节点的信息,维护线段树的性质。

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 ;
}

线段树的区间查询

对于一个区间 [L,R] 的查询,必定能分成不超过 logn 个子区间进行分别查询。

如果现在查到了区间 [l,r],如果 LlrR,则直接返回该区间的值,否则对该区间进行分治查询。时间复杂度为 O(logn)

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;
}

线段树的区间修改

在区间修改时,如果某个节点 P,被修改区间 [l,r] 全部覆盖,那么 PP 的子树全部要修改,时间复杂度最高位 O(n)

而且,如果 P 修改了之后没有被查询到,那么修改 P 就是徒劳的。

lazy tag

lazy tag 给 P 节点增加标记,代表“此节点有更新,但子节点尚未被更新”。

如果在后续指令中,需要从节点 P 向下递归,那么:

  1. 检查标记状态,根据标记更新 P 的子节点。
  2. P 的子节点更新标记状态。
  3. 删除 P 的标记状态。

这样做,区间修改的复杂度就降至 O(logn)

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。

例题 1 SP1716

本题在每个节点中,除了维护区间端点外,还要维护以下 4 个值:

区间和 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 均为 a[l]

#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;
}

例题 2 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;
}
posted @   11jiang08  阅读(16)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 在鹅厂做java开发是什么体验
· 百万级群聊的设计实践
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战
· 永远不要相信用户的输入:从 SQL 注入攻防看输入验证的重要性
· 全网最简单!3分钟用满血DeepSeek R1开发一款AI智能客服,零代码轻松接入微信、公众号、小程
点击右上角即可分享
微信分享提示