线段树简单入门
线段树
线段树的定义
线段树, 顾名思义, 就是每个节点表示一个区间.
线段树通常维护一些区间的值, 例如区间和.
比如, 上图 \([2, 5]\) 区间的和, 为以下区间的和的和:
我们可以这样定义线段树的一个节点:
struct node {
int sum; // 维护该节点表示区间的和
int l, r; // 表示该节点表示的左右区间 (然而实现中常常不需要存储, 后面会说到)
int lc, rc; // 表示该节点的左右孩子 (然而实现中常常不需要存储, 后面会说到)
};
实现中, 我们常常使用完全二叉树来表示线段树, (如果你写过二叉堆的话你会知道要如何表示) 而在完全二叉树中一个节点的左孩子右孩子可以很方便的求出.
(若线段树不是完全二叉树, 可以假装它是完全二叉树, 毕竟这样比较方便)
因为常常我们只需要存储一个值 sum
, 于是下文只存储了一个数组 seg[]
代表所有节点的 sum
值.
线段树的基础操作
例题
已知一个数列,你需要进行下面两种操作:
将某一个数加上 \(x\)
求出某区间每一个数的和
\(0 \le n \le 2\times 10^5\)
更新节点
直接拿左孩子右孩子更新就可以了.
void Updata(int x) {
seg[x] = seg[x << 1] + seg[x << 1 | 1];
}
建树
暴力建就可以了.
void Build(int x, int l, int r, int a[]) { // 给 a[] 数组建线段树
if(l < r) {
int mid = (l + r) >> 1;
Build(x << 1, l, mid);
Build(x << 1 | 1, mid + 1, r);
Updata(x);
} else seg[x] = a[l];
}
修改节点
递归修改, 然后更新.
// cur 表示我们目前查询到了的节点编号
// l, r 表示 cur 这个节点的编号
// 将 q 改为 k
void Modify(int cur, int l, int r, int q, int k) {
if(ql <= l && r <= qr) { // 如果这个节点被 ql, qr 包含, 就返回这个节点的值
seg[cur] = k;
} else {
int mid = (l + r) >> 1; // 左右子树的区间分别为 [l, mid], [mid + 1, r]
if(q <= mid) Modify(cur << 1, l, mid, q, k); // 要修改的节点在左孩子
else Modify(cur << 1 | 1, mid + 1, r, q, k); // 要修改的节点在右孩子
Updata(cur);
}
}
查询区间和
// cur 表示我们目前查询到了的节点编号
// l, r 表示 cur 这个节点的编号
// ql, qr 表示要查询的节点编号
int Query(int cur, int l, int r, int ql, int qr) {
if(ql <= l && r <= qr) return seg[cur]; // 如果这个节点被 ql, qr 包含, 就返回这个节点的值
int mid = (l + r) >> 1, ret = 0; // 左右子树的区间分别为 [l, mid], [mid + 1, r]
if(ql <= mid) ret += Query(cur << 1, l, mid, ql, qr);
if(qr > mid) ret += Query(cur << 1 | 1, mid + 1, r, ql, qr);
return ret;
}