线段树
线段树是一种基于分治思想的二叉树,用于在区间上进行信息统计。包括单点修改、区间修改、区间查询(区间求和、区间最大值、区间最小值)。时间复杂度为
线段树的定义
- 线段树的每个节点都代表一个区间。
- 线段树具有唯一根节点,代表整个统计区间。
- 线段树的每个叶子节点都代表一个长度为
的区间。 - 对于每个内部节点
,它的左右子节点分别代表区间 和 ,其中 。
如图:

注意:保存线段树的数组长度为
关于线段树的空间消耗:
线段树的操作
以区间最大值问题为例:
线段树的结构:
cpp
const int N = 1e5 + 10;
struct SegmentTree {
int l, r, dat;
} t[N * 4];
void pushup(int p) {
t[p].dat = max(t[p << 1].dat, t[p << 1 | 1].dat);
}建树
cpp
// 构建线段树,调用入口 build(1, 1, n);
void build(int p, int l, int r) {
t[p].l = l, t[p].r = r; // 节点 p 代表区间 [l, r]
if (l == r) { // 叶子节点
t[p].dat = a[l];
return;
}
int mid = (l + r) >> 1;
build(p << 1, l, mid); // 构造左子树 [l, mid]
build(p << 1 | 1, mid + 1, r); // 构造右子树 [mid + 1, r]
pushup(p); // 自下而上传递信息
}单点修改
单点修改指令形如:C x v,表示把序列
cpp
// 单点修改,把 a[x] 的值更改为 v,调用入口 modify(1, x, v);
void modify(int p, int x, int v) {
if (t[p].l == x && t[p].r == x) { // 找到 x 对应的叶子节点
t[p].dat = v;
return;
}
int mid = (t[p].l + t[p].r) >> 1;
if (x <= mid) modify(p << 1, x, v); // x 在左子树
else modify(p << 1 | 1, x, v); // x 在右子树
pushup(p); // 自下而上传递信息
}区间查询
区间查询指令形如:Q l r,表示查询序列
cpp
// 区间查询,查询序列 a 在区间 [l, r] 上的最大值,调用入口 query(1, l, r);
int query(int p, int l, int r) {
if (t[p].l >= l && t[p].r <= r) return t[p].dat; // 当前区间被完全覆盖
int mid = (t[p].l + t[p].r) >> 1;
int ans = -1e9;
if (l <= mid) ans = max(ans, query(p << 1, l, r)); // 当前区间与左子树有交集
if (r > mid) ans = max(ans, query(p << 1 | 1, l, r)); // 当前区间与右子树有交集
return ans;
}区间修改
如果要修改区间
懒惰标记即是通过延迟对节点信息的更改,从而减少可能不必要的操作次数。即我们在执行修改指令时,在满足
以区间求和问题为例:
cpp
const int N = 1e5 + 10;
struct SegmentTree {
int l, r, dat;
int lazy; // 懒惰标记
} t[N * 4];
void pushup(int p) {
t[p].dat = t[p << 1].dat + t[p << 1 | 1].dat;
}
void pushdown(int p) {
if (t[p].lazy) {
t[p << 1].dat += t[p].lazy * (t[p << 1].r - t[p << 1].l + 1);
t[p << 1 | 1].dat += t[p].lazy * (t[p << 1 | 1].r - t[p << 1 | 1].l + 1);
t[p << 1].lazy += t[p].lazy;
t[p << 1 | 1].lazy += t[p].lazy;
t[p].lazy = 0;
}
}
// 区间修改,修改序列 a 在区间 [l, r] 上的值,调用入口 modify(1, l, r, v);
void modify(int p, int l, int r, int v) {
if (t[p].l >= l && t[p].r <= r) {
t[p].dat += v * (t[p].r - t[p].l + 1);
t[p].lazy += v;
return;
}
pushdown(p);
int mid = (t[p].l + t[p].r) >> 1;
if (l <= mid) modify(p << 1, l, r, v);
if (r > mid) modify(p << 1 | 1, l, r, v);
pushup(p);
}
// 带有懒惰标记的区间查询,在裂开时需要下传懒惰标记
int query(int p, int l, int r) {
if (t[p].l >= l && t[p].r <= r) return t[p].dat;
pushdown(p); // 下传懒惰标记
int mid = (t[p].l + t[p].r) >> 1;
int ans = 0;
if (l <= mid) ans += query(p << 1, l, r);
if (r > mid) ans += query(p << 1 | 1, l, r);
return ans;
}