小Y的线段树:离线删除线段树¶
这是出题人的离线线段树方案,由 alternative.cpp 实现。位置 \(i\) 在前缀 \([1,r]\) 的最终栈中保留,当且仅当右侧没有不小于它的已读元素;该判据不依赖区间左端点。因此,询问 \([l,r]\) 等于当前前缀栈在这个区间内的元素数。
维护存活位置¶
建立线段树,每个叶子对应一个位置,初始存活数为一、最小值为该位置的数值。内部节点保存两儿子的存活数之和,以及存活元素的最小值。删除叶子后,计数变零,最小值设为大于全部合法值的正无穷。
将询问按右端点排序,逐个将扫描位置推进到当前询问的右端点。加入位置 \(r\) 时,删除 \([1,r-1]\) 中所有数值不大于 \(a_r\) 的存活位置:
- 若线段树区间起点大于 \(r-1\) ,或最小值大于 \(a_r\) ,直接返回。
- 到达符合条件的叶子时删除它。
- 其他情况递归处理儿子,再合并计数与最小值。
所有叶子从一开始已建立,未扫描后缀也暂时存活;删除操作限制在前缀,询问又限制在 \([l,r]\) ,所以后缀不会影响结果。处理完 \(r\) 后,做普通区间计数查询,并按原询问编号保存答案。
正确性与复杂度¶
扫描后,已读位置中存活的恰是尚未遇到右侧不小于自身值的位置。归纳每次删除操作,正好删除此时失去资格的所有位置,且保留其余位置。因此存活集合与前缀栈相同,区间计数给出正确答案。
每个位置最多删除一次,每次删除所涉及的祖先至多 \(O(\log N)\) .没有删除发生的分支在最小值条件处立即剪枝;前缀边界另产生每次至多 \(O(\log N)\) 的路径开销。所以所有删除共 \(O(N\log N)\) ,不是每次扫描整个树。加上询问排序和区间查询,总时间 \(O(N\log N+M\log M+M\log N)\) ,空间 \(O(N+M)\) .
该方法覆盖完整范围。首选分桶单调栈避免线段树删除的对数开销。出题人提供离线线段树思路,GPT-6 完成正确推进边界、删除条件、证明和 C++17 实现。性能见测量记录。