线段树必须手动调用build()初始化,否则seg[1]为垃圾值;建树需递归覆盖[l,r],叶子赋值arr[l],数组大小至少4*n;push_down()须在进入子区间前调用,且正确传递lazy标记。

线段树必须手动建树,build() 不能省
线程树不是靠容器自动扩容的结构,不调用 build() 就直接查或改,seg[1] 是垃圾值,所有查询返回随机数。常见错误是只写 update() 和 query(),忘了初始化——尤其用全局数组时,seg[] 和 lazy[] 默认为 0,但叶子节点没对应到原始数组值,整个树就是空壳。
实操建议:
立即学习“C++免费学习笔记(深入)”;
- 建树必须递归覆盖区间
[l, r],叶子节点赋值seg[node] = arr[l](下标从 0 开始时注意边界) - 数组大小至少开
4 * n,别信“2 * n够用”的经验——满二叉树高度为⌈log₂n⌉ + 1,叶子层最多2^⌈log₂n⌉个,总节点数接近4n - 如果用 vector 动态建树,别在
build()前只 reserve(4*n),要 resize(4*n, 0),否则访问seg[2*node]可能越界
push_down() 的触发时机和 lazy 标记清零逻辑
懒标记不是“有就下传”,而是“当前节点有 lazy 且需要继续往下走时才下传”。典型错误:在 query() 进入子区间前没调 push_down(),导致子树没更新,查到旧值;或者在 push_down() 里把 lazy[node] 设为 0 后,忘了给左右子节点的 lazy 累加(加法更新)或覆盖(赋值更新)。
实操建议:
立即学习“C++免费学习笔记(深入)”;
- 所有进入子区间的操作(
query()分支、update()递归进子树前)都必须先push_down(node, l, r) -
push_down()中:先计算中点mid = (l + r) >> 1,再更新左子seg[2*node]和右子seg[2*node+1],最后设lazy[node] = 0 - 若支持多种更新类型(如加法 + 赋值),lazy 数组得是 struct,不能只用 int;加法场景下子节点 lazy 要 +=,赋值场景下要 =
区间更新时的递归边界判断:别漏掉 if (r qr)
线段树递归终止条件有两个:完全包含(直接处理)和完全不交(直接 return)。漏掉“不交”判断会导致无意义递归,轻则多跑几层,重则栈溢出或访问非法内存(比如 r = -1 时还继续算 mid)。
实操建议:
立即学习“C++免费学习笔记(深入)”;
- 每个递归函数开头统一写:
if (r qr) return;(注意是ql/qr,不是l/r) - 然后写
if (ql 处理完全覆盖 - 不要把两个条件合并成
if (ql = ql && l ——后者逻辑重复且易错 - 调试时可在 return 前加
assert(l ,避免因初始参数错(如传了 <code>l=1, r=0)引发后续崩溃
离散化后建树:原数组下标和线段树位置不是一一对应的
当值域大(如 1e9)、但实际出现点少(如 2e5 个)时,必须离散化。这时容易误以为“排序去重后的第 i 个数对应线段树位置 i”,但线段树维护的是**索引区间**,不是值本身。比如离散化后得到 vals = {2, 5, 11, 100},那修改“值为 5 的位置”其实是修改 pos = lower_bound(vals.begin(), vals.end(), 5) - vals.begin() + 1 对应的叶子节点,不是直接拿 5 当下标。
实操建议:
立即学习“C++免费学习笔记(深入)”;
- 离散化后得到映射数组
vals,所有查询/更新的数值都要先lower_bound找到 rank(从 0 开始),再转成线段树里的位置(通常 +1 做 1-indexed) - 区间查询 [L, R] 时,不是查线段树里
L到R,而是查rank_L到rank_R,其中rank_L = lower_bound(..., L),rank_R = upper_bound(..., R) - 1 - 别在离散化后还用原数组长度
n建树,要用去重后大小m = vals.size()
线段树最难的从来不是写对四个函数,而是每次递归入口的参数是否真代表当前节点管辖的连续整数区间——这个区间必须和你传进去的 l、r 完全一致,差 1 都会崩。










