1. 线段树:区间操作的底层逻辑
第一次接触线段树是在ACM竞赛的训练赛上,当时遇到一道需要频繁查询数组区间和的题目。用传统的遍历方法时间复杂度太高,而前缀和又无法应对动态更新的场景。当队友掏出线段树解法时,那O(logN)的查询效率让我瞬间被这种数据结构圈粉。
线段树本质上是用空间换时间的典型代表。它通过将原始区间不断二分,构建出一棵完全二叉树。每个叶子节点对应原始数组的一个元素,而非叶子节点则存储其子节点信息的聚合值(如区间和、最大值等)。这种结构使得:
- 单点更新只需修改对应叶子节点到根节点路径上的所有节点(O(logN))
- 区间查询只需合并若干个子树的信息(O(logN))
关键认知:线段树的效率来源于对区间信息的预计算和存储,这与动态规划的思想有异曲同工之妙。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 线段树的实现细节剖析
2.1 存储结构与建树
常见的存储方式有两种:
- 指针形式(适合理解但实际较少使用)
- 数组模拟(竞赛常用,节省空间)
以数组存储为例,我们需要开一个足够大的数组(通常是原始数据大小的4倍):
cpp复制const int MAXN = 1e5 + 5;
int tree[MAXN << 2]; // 线段树数组
int arr[MAXN]; // 原始数据
建树过程(递归实现):
cpp复制void build(int p, int l, int r) {
if (l == r) {
tree[p] = arr[l];
return;
}
int mid = (l + r) >> 1;
build(p<<1, l, mid);
build(p<<1|1, mid+1, r);
tree[p] = tree[p<<1] + tree[p<<1|1]; // 区间和示例
}
踩坑提醒:数组大小必须是2的幂次再乘以2,否则可能越界。实际使用时建议直接开4倍空间。
2.2 区间查询的实现技巧
查询操作需要考虑三种情况:
- 查询区间完全包含当前节点区间 → 直接返回节点值
- 查询区间与当前节点区间无交集 → 返回不影响结果的值(如求和时返回0)
- 查询区间部分重叠 → 继续
