线段树

前言

线段树是算法竞赛中常用的用来维护 区间信息 的数据结构。

线段树可以在 的时间复杂度内实现单点修改、区间修改、区间查询(区间求和,求区间最大值,求区间最小值)等操作。

线段树维护的信息,需要满足可加性

基本结构

image-20210127122037642
  • 如图,从原数组 a 生成线段树d 的一个情况。第 i 号节点存储的是第 $2×i,2×i+1 $号节点的信息和

  • 假设 i 号节点范围 [l,r] , m i d = ( l + r ) / 2 mid=(l+r)/2 mid=(l+r)/2,则: i × 2 i×2 i×2 范围: [ l , m i d ] [l,mid] [l,mid] i × 2 + 1 i×2+1 i×2+1 范围: [ m i d + 1 , r ] [mid+1,r] [mid+1,r](如图上i=1时范围[1,5],(1+5)/2=3,那么2的范围[1,3],3的范围[4,5]

建树

若有 n n n 个叶子结点,懒得计算的话可以直接把数组长度设为 4 n 4n 4n

(代码中数组a对应图中a,数组tree对应图中d)

//递归回溯
void push_up(int rt){
    tree[rt]=tree[rt*2] + tree[rt*2+1];
}
// 对 [l,r] 区间建立线段树,当前根的编号为 rt
void build(int rt, int l, int r) {
	if (l == r){
		tree[rt] = a[l];
		return;
	}
	int mid = (l + r) / 2;
	build(rt * 2, l, mid);
	build(rt * 2 + 1, mid + 1, r);
    // 递归对左右区间建树
	push_up(rt);
}

线段树的区间查询

原理和复杂度

查询 a q l   a q r a_{ql}\text{~}a_{qr} aql aqr 的信息:

① 如果是子区间之间返回值。

② 比较 ql 和左儿子范围之间关系,如果存在元素在左儿子代表集合内,则进左儿子。

③ 右儿子同理。

④ 合并左右儿子查询信息。

复杂度 O ( l o g ⁡ n ) O(log⁡n) O(logn)

code

//懒标记下传
void push_down(int rt, int l, int r) {
    if (lazy[rt]) {
        //左右子树更新
        int len = r-l+1;
        tree[rt*2] += (len-(len/2)) * lazy[rt];
        tree[rt*2+1] += (len/2) * lazy[rt];
        //左右懒惰标记更新
        lazy[rt*2] += lazy[rt];
        lazy[rt*2+1] +=lazy[rt];
        //取消该节点标记
        lazy[rt] = 0;
    }
}
// [beg,end] 为查询区间,[l,r] 为当前节点包含的区间,rt为当前节点的编号
int getsum(int rt,int l,int r,int beg,int end) {
    // 当前区间为询问区间的子集时直接返回当前区间的和
    if (beg <= l && r <= end) 
        return tree[rt];
    // 如果当前节点的懒标记非空,则更新当前节点两个子节点的值和懒标记值
    if(lazy[rt]) 
        push_down(rt, l, r); 
    
    int mid=l+r>>1,res=0;
    // 如果左儿子代表的区间 [l,m] 与询问区间有交集,则递归查询左儿子
    if (beg <= mid) 
        res += getsum(rt*2,l,mid,beg,end);//???+= 还是=
    if (end > mid) 
        res += getsum(rt*2+1,mid+1,r,beg,end);
    // up(rt);
    return res;
}

线段树的区间修改

修改 a q l → a q r a_{ql}→a_{qr} aqlaqr 的信息(同加一个值)

code

// rt为当前节点的编号,[beg,end]为修改区间,[l,r]为当前节点包含的区间,k为被修改的元素的变化量
void update(int rt,int l,int r,int beg,int end,int k) {
    // 当前区间为修改区间的子集时直接修改当前节点的值,然后打标记,结束修改
    if (l>=beg && r<=end){
        tree[rt] += (r-l+1)*k;
        lazy[rt] += k;
        return;
    }
    if(lazy[rt]) 
        push_down(rt, l, r); 
    int mid=l+r>>1;
    if (beg<=mid) 
        update(rt*2,l,mid,beg,end,k);
    if (end>mid) 
        update(rt*2+1,mid+1,r,beg,end,k);
    push_up(rt);
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值