C++怎么实现线段树_C++区间查询与更新【数据】

2次阅读

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

C++怎么实现线段树_C++区间查询与更新【数据】

线段树必须手动建树,build() 不能省

线程树不是靠容器自动扩容的结构,不调用 build() 就直接查或改,seg[1] 是垃圾值,所有查询返回随机数。常见错误是只写 update()query(),忘了初始化——尤其用全局数组时,seg[]lazy[] 默认为 0,但叶子节点没对应到原始数组值,整个树就是空壳。

实操建议:

  • 建树必须递归覆盖区间 [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 累加(加法更新)或覆盖(赋值更新)。

实操建议:

  • 所有进入子区间的操作(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)。

实操建议:

  • 每个递归函数开头统一写: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 当下标。

实操建议:

  • 离散化后得到映射数组 vals,所有查询/更新的数值都要先 lower_bound 找到 rank(从 0 开始),再转成线段树里的位置(通常 +1 做 1-indexed)
  • 区间查询 [L, R] 时,不是查线段树里 LR,而是查 rank_Lrank_R,其中 rank_L = lower_bound(..., L)rank_R = upper_bound(..., R) - 1
  • 别在离散化后还用原数组长度 n 建树,要用去重后大小 m = vals.size()

线段树最难的从来不是写对四个函数,而是每次递归入口的参数是否真代表当前节点管辖的连续整数区间——这个区间必须和你传进去的 lr 完全一致,差 1 都会崩。

text=ZqhQzanResources