概念:
线段树是一棵完美二叉树,所有叶子的深度相同,并且每个节点要么是叶子要么是有 2 个儿子的树,树上的每个节点都维护一个区间 (最底层的节点退化到维护点) 。根是维护整个区间的,每个节点维护的是父亲的区间二等分后的其中一个子区间。
由于其二叉树的结构,可以满足当有 n 个元素时,对区间的操作可以在 的时间内完成。因此,对于一些给出连续数据的问题,线段树可以在
内很快的实现修改和询问。
根据节点中维护的数据不同,线段树可以提供不同的功能。
定义:
线段树的定义一般用数组实现
- origin数组 存储的是原始数据,也就是需要用来建树操作的那一段连续数据
- tree数组 存储的就是树的信息了,也是树的结构体现。通过改变存储的值,实现不同功能
- lazy数组 存储懒惰信息,在 pushdown的时候用到,向下更新
tree 和 lazy 数组的大小应该是 origin数组的 4 倍左右。lazy数组 只有当树有区间更新时才需要定义。
const int MAXN=1e5 + 10;
int origin[MAXN], tree[MAXN<<2], lazy[MAXN<<2];
建树:
这里有几个变量
- p 表示当前节点编号。左节点编号是 p << 1,右节点编号是 p << 1 | 1。分别表示 2*p,2*p+1
- l、r 是在 origin数组 的位置,分别表示区间的左右端点
l、r 表示当前的操作区间,初始的调用时一定是 1、n,在递归中会不断改变。build函数一般不用改。
void build(int p, int l, int r) {
if (l == r) {
tree[p] = origin[l];
return;
}
int mid = (l + r) >> 1;
build(p << 1, l, mid);
build(p << 1 | 1, mid + 1, r);
pushup(p);
}
向上更新节点的函数。准确的说,线段值是在这里产生的,若要改线段树存储数据类型,最先改的是这个。
void pushup(int p) {
tree[p]=tree[p << 1]+tree[p << 1 | 1];
}
向下更新节点的函数,用了懒惰的思想。 当且仅当有区间更新时才需要用到。点更新、查询都不需要。
void pushdown(int p, int l, int r) {
if (lazy[p]) {
lazy[p << 1] += lazy[p];
lazy[p << 1 | 1] += lazy[p];
int mid = (l + r) >> 1;
tree[p << 1] += lazy[p] * (mid - l + 1);//左右儿子结点均按照需要加的值总和更新结点信息
tree[p << 1 | 1] += lazy[p] * (r - mid);
lazy[p] = 0;
}
}
更新:
点更新。由于点是最底层的,不需要向下更新,所以不包含 pushdown函数。但若夹杂着区间更新,则要加上。
void update_node(int p, int l,int r, int q,int v) {
if (l == r) { //查询到点
tree[p] += v;
return;
}
int mid = (l + r) >> 1;
//pushdown(p, l, r); 若既有点更新又有区间更新,需要这句话
if (q > mid) update_node(p << 1 | 1, mid + 1, r, q, v);
else update_node(p << 1, l, mid, q, v);
pushup(p);
}
区间更新。传给子节点时用到了“懒惰”的思想 (lazy数组),需要用到 pushdown函数。
void update(int p, int l, int r, int ql, int qr, int v) { //区间更新
if (ql <= l && r <= qr) { //当前区间在更新区间内
tree[p] += v * (r - l + 1);
lazy[p] += v;
return;
}
int mid = (l + r) >> 1;
pushdown(p, l, r);
if (ql <= mid) update(p << 1, l, mid, ql, qr, v);
if (qr > mid) update(p << 1 | 1, mid + 1, r, ql, qr, v);
pushup(p);
}
查询:
查询的操作经常需要改写,可以实现五花八门的功能。修改时,第6、7行一般是不需要改的,最多在 if 的判断语句中加条件。
int query(int p, int l, int r, int ql, int qr) {
if (ql <= l && r <= qr) return tree[p]; //被包含在询问区域内的区间(有效的部分)
int mid = (l + r) >> 1;
//pushdown(p, l, r); //有区间更新时才需要
int temp = 0;
if (qr > mid) temp += query(p << 1 | 1, mid + 1, r, ql, qr); //分块切割出有效的部分(已忽略无效部分)
if (ql <= mid) temp += query(p << 1, l, mid, ql, qr);
return temp;
}
注意:调用时,若有多组测试用例,一定别忘了初始化 lazy数组
先总结这么多,有新的收获后添加。