前言
线段树是算法竞赛中常用的用来维护 区间信息 的数据结构。
线段树可以在 的时间复杂度内实现单点修改、区间修改、区间查询(区间求和,求区间最大值,求区间最小值)等操作。
线段树维护的信息,需要满足可加性
基本结构
![image-20210127122037642](https://i-blog.csdnimg.cn/blog_migrate/75f109afd579107c9784444fbde10b69.png)
-
如图,从原数组 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(logn) 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} aql→aqr 的信息(同加一个值)
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);
}