[数据结构]线段树

线段树

模板题:线段树1-洛谷(代码)

线段树是一种平衡二叉树,其核心思想为二分+分治+懒标记,可在 O ( log ⁡ 2 n ) O(\log_2n) O(log2n)的复杂度内完成单点修改、区间修改、区间查询等操作。

设原序列长度为 n n n,则每个节点 i d ( 1 ≤ i d ≤ 4 × n ) id(1\le id \le 4\times n) id(1id4×n)都有一段原序列的管理区间 [ L , R ] [L,R] [L,R]

  • L = R L=R L=R,则其为叶子节点,其管理区间长度为 1 1 1,存储即为原序列数据。 t r e e [ i d ] = a [ l ] tree[id]=a[l] tree[id]=a[l]

  • L < R L<R L<R,则为非叶子节点,其管理区间内存储了该区间内的某种信息(如区间最值、区间GCD、区间和等)。每个节点 i d id id有两个孩子,左孩子为 2 × i d 2\times id 2×id,管理区间为 [ L , M ] [L,M] [L,M];右孩子为 2 × i d + 1 2\times id+1 2×id+1,管理区间为 [ M + 1 , R ] [M+1,R] [M+1,R] M = L + R 2 M=\frac{L+R}{2} M=2L+R t r e e [ i d ] = o p t ( t r e e [ 2 × i d ] , t r e e [ 2 × i d + 1 ] ) tree[id]=opt(tree[2\times id],tree[2\times id+1]) tree[id]=opt(tree[2×id],tree[2×id+1])

在这里插入图片描述

线段树适用条件:大区间的值,可由若干个小区间的值合并而来。如可重复贡献性问题、区间之和、区间之积等。

int n,v[n];
struct node{
    long long data,tag=0;//管理区间内的数据,懒标记标签
}tree[n<<2];//线段树数组开4*n
int opt(int a,int b);//具体操作函数

建树

  • 向下递归:先左后右,向下递归到叶子节点,向叶子节点中传入原数组值
  • 向上传递:叶子节点被传入数据后,需组织成区间信息,并向其父节点传递(向上传递)
void pushup(int id){//向上传递
    tree[id].data=opt(tree[id<<1].data,tree[id<<1|1].data);
}
void build(int id,int l,int r){//id:当前递归节点 [l,r]:当前递归节点管理区间
    if(l==r) tree[id].data=a[l];//已递归到叶子节点
    else{
        int mid=l+r>>1;
        build(id<<1,l,mid);//先递归左孩子2*id,管理区间[l,mid]
        build(id<<1|1,mid+1,r);//然后递归到右孩子2*id+1,管理区间[mid+1,r]
        pushup(id);//向上传递
    }
}
//调用:build(1,1,n);

单点修改

设将 p o s pos pos处修改为 v a l u e value value

  • 向下递归:递归至叶子节点找pos。若 p o s ≤ m i d pos\le mid posmid,则进入左孩子管理区间继续递归;若 p o s > m i d pos>mid pos>mid,则进入右孩子管理区间继续递归
  • 向上传递:修改后向上传递信息
void change(int id,int l,int r,int pos,int value){//当前递归节点及其管理区间
    if(l==r) tree[id].data=value,a[pos].data=value;//递归至叶子节点修改信息
    else{
        int mid=l+r>>1;
        if(pos<=mid) change(id<<1,l,mid);//若pos在[l,mid],进入左孩子管理区间
        else chage(id<<1|1,mid+1,r);//若pos在[mid+1,r],进入右孩子管理区间
        pushup(id);
    }
}

区间修改

关键操作:懒标记(Lazy Tag)。对每个节点定义区间标记 t a g tag tag,用于记录其管理区间 [ L , R ] [L,R] [L,R]内的统一修改,而对其中每个元素先不进行修改。仅当该管理区间元素的修改一致性被破坏时,才将自身 t a g tag tag向下传递给左右孩子,并清空自身 t a g tag tag。懒标记的作用就是等待下次向下传递时递归给左右孩子。

算法流程:设修改区间 [ m l , m r ] [ml,mr] [ml,mr],当前递归区间 [ l , r ] [l,r] [l,r]

  • [ l , r ] ⊂ [ m l , m r ] [l,r]\subset[ml,mr] [l,r][ml,mr](完全被修改区间覆盖),则直接将 [ l , r ] [l,r] [l,r]打标记
  • 否则无法被修改区间完全覆盖,说明当前递归区间存在不应被修改的元素。向下对其左右子树传递区间标记,并分别递归其左右子树。
void addtag(int id,int l,int r,int d){//为节点打标记,此处以+d为例
    tree[id]+=d,tree[id]+=d*(r-l+1);
}
void pushdown(int id,int l,int r){//向下对左右孩子传递标记
    if(tree[id].tag){
        int mid=l+r>>1;
        addtag(id<<1,l,mid,tree[id].tag);
        addtag(id<<1|1,mid+1,r,tree[id].tag);
        tree[id].tag=0;
    }
}
void modify(int id,int l,int r,int ml,int mr,int d){//以区间[ml,mr]均+d为例
    if(ml<=l&&r<=mr){
        addtag(id,l,r,d);
    }else{
        pushdown(id,l,r);
        int mid=l+r>>1;
        if(ml<=mid) modify(id<<1,l,mid,ml,mr,d);
        if(mr>mid) modify(id<<1|1,mid+1,r,ml,mr,d);
        pushup(id);
    }
}

区间查询

设查询区间 [ q l , q r ] [ql,qr] [ql,qr],当前递归节点 i d id id管理区间为 [ l , r ] [l,r] [l,r]

  • [ l , r ] ⊂ [ q l , q r ] [l,r]\subset[ql,qr] [l,r][ql,qr]:直接返回tree[id].data
  • [ q l , q r ] [ql,qr] [ql,qr] [ l , r ] [l,r] [l,r]部分重叠,则下传懒标记,并分别递归其左右孩子
int query(int id,int l,int r,int ql,int qr){
    if(ql<=l&&r<=qr) return tree[id].data;
    pushdown(id,l,r);
    int mid=l+r>>1,ans ;//ans=0(区间之和) 1(区间之积) MAX/MAX+1(MAX/MIN)
    if(ql<=mid) ans=opt(ans,query(id<<1,l,mid,ql,qr));
    if(qr>mid) ans=opt(ans,query(id<<1|1,mid+1,r,ql,qr));
    return ans;
}
posted @   椰萝Yerosius  阅读(5)  评论(0编辑  收藏  举报  
相关博文:
阅读排行:
· winform 绘制太阳,地球,月球 运作规律
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· AI 智能体引爆开源社区「GitHub 热点速览」
· Manus的开源复刻OpenManus初探
· 写一个简单的SQL生成工具
历史上的今天:
2023-12-28 学习心得:函数指针和函数指针数组
点击右上角即可分享
微信分享提示