线段树
线段树是一种二叉搜索树
==========================
储存方式
struct node{
int l, r; //当前节点的区间范围
int v; //其他需要储存的信息
} tr[N];
需要储存的信息,需要看目前的信息,父节点可否由子节点信息直接得到,若不能需要增加新的储存信息(具体见例题2,3)
u点的左儿子 2u u << 1 即 tr[u << 1]
u点的右儿子 2u+1 u << 1 | 1 即 tr[u << 1 | 1]
五大基本操作
1.pushup 通过子节点信息,计算父节点信息 - logn
2.build 将一段区间初始化成线段树(建立)
3.modify 修改某个点或某个区间
4.query 查询某一段区间
5.pushdown 将父节点的修改传到子节点(见例题3,4(两个懒标记))
单点修改,区间查询
区间修改,区间查询
区间修改,单点查询
===========================
0.pushup
通过子区间信息更新父区间信息
一般放在build函数和modify函数的最后
eg:更新父区间的和
void pushup(int u){
tr[u].sum = tr[u << 1].sum + tr[u << 1| 1].sum;
}
1.build
传入当前节点编号u,左端点l,右端点r
void build(int u, int l, int r){
tr[u].l = l; 存当前节点的左编号
tr[u].r = r; 存当前节点的右编号
if ( l == r ) return; 判断是否是叶子节点
int mid = l + r >> 1; 当前区间得中点
build(u << 1, l, mid); 递归到左儿子
build(u << 1 | 1, mid + 1, r); 递归到右儿子
(一般在此处写pushup操作)
}
2.query
假设当前查询的区间为[l,r] 查询的节点范围为[tl,tr]
1.[l,r]包含在[tl,tr]区间内
直接返回
2.[l,r]不包含在[tl,tr]但是有交集(每次递归后判断是情况1还是2)
1.和左边有交集 -> 递归到左边
2.和右边有交集 -> 递归到右边
3.左边和右边都有交集 -> 两边都递归**(对于每次访问,只会执行一次)**
访问的时间复杂度在logn内(最多4logn)
eg:找当前区间的最大值
int query(int u, int l, int r) {
int v = 0; //最大值
if ( tr[u].l >= l && tr[u].r <= r ) return tr[u].v; //若访问的区间在要求区间内
else{
int mid = tr[u].l + tr[u].r >> 1;
if ( mid >= l ) v = query(u << 1, l, r); //中点在l右边则递归左儿子
if ( mid < r ) v = max(v, query(u << 1 | 1 ,l ,r)); //不取等,因为右儿子是从mid+1开始
}
return v;
}
3.modify
假设当前访问的区间为[l,r] 修改的区间范围为[tl,tr]
1.若tr[l,r]是叶节点
直接修改
2.若要修改的区间的中点在l右边(大于或等于)
递归左儿子
3.若要修改的区间的中点在r左边(小于,建树时右区间从mid+1开始,不取等于)
递归右儿子
修改完回溯时要记得用pushup函数更新父节点信息
eg:给x处加上v
void modify(int u, int x, int v){
if ( tr[u].l == tr[u].r ) tr[u].v = v; //若是叶节点则赋值
else{
int mid = tr[u].r + tr[u].l >> 1;
if ( mid >= x ) modify(u << 1, x, v); //同上,区间变成了点
else modify(u << 1 | 1, x, v);
pushup(u); //回溯时通过子节点,更新父节点的最大值
}
}
4.pushdown
实现区间修改,区间查询
时间复杂度o(logn)
作用:将父节点的修改传到子节点
若不用懒标记,区间修改的时间复杂度最坏o(n),过慢
pushdown一般放在modify和query前面
add懒标记
-> 在当前节点为跟的子树中的每一个节点加上add,不包含当前区间自己(看个人习惯)
从上往下递归时,将add标记传到子区间,并删去当前节点的标记 -> pushdown
eg:
跟节点为root,左子区间为left,右子区间为right,pushdown操作如下
left.add += root.add //将父区间懒标记add传入左子区间
left.sum += (left.r - left.l + 1) * root.add //更新左子区间的信息(这里是总和的例子)
right.add += root.add //将父区间的懒标记add传入右子区间
right.sum += (right.r - right.l + 1) * root.add //更新右子区间的信息(这里是总和的例子)
root.add = 0; //删去父区间的懒标记
在修改或询问操作,向左右子区间分裂时