如何通过预计算以亚线性时间统计数组中含k的相邻元素区间数?
没问题,我来帮你梳理这个问题,然后给出几种能支持亚线性时间查询的预计算方案,都是高层思路,不用纠结具体代码细节:
问题核心先理清楚
咱们先把问题拆解明白:给定n个元素的数组A,我们有n-1个相邻元素对(对应索引i从0到n-2),每个i对应一个区间[L_i, R_i]——其中L_i = min(A[i], A[i+1]),R_i = max(A[i], A[i+1])。每次查询会给你三个值a、b、k,需要统计i在[a, b-1]范围内的所有i中,满足L_i ≤ k ≤ R_i的数量。目标是通过预计算,让每次查询的时间复杂度低于线性(也就是亚线性,比如O((log n)²)这种级别)。
方案1:线段树+二分查找(在线查询首选)
这个方案适合在线场景(查询是随时来的,不能提前知道所有查询),核心是用线段树存储每个区间的预处理信息,再用二分快速统计:
- 预处理阶段:
- 建一棵线段树,每个叶子节点对应一个i(0到n-2),存储该i的
L_i和R_i。 - 对于线段树的每个内部节点,收集它覆盖的所有i的
(L_i, R_i)对,然后按L_i从小到大排序,得到两个数组:sorted_L(排序后的L值)和对应的sorted_R(和sorted_L一一对应的R值)。 - 对每个内部节点的
sorted_R数组,提前把每个前缀(比如前1个、前2个…前m个元素,m是该节点覆盖的i的数量)都排序好,这样之后可以快速统计前缀中≥k的元素数量。
- 建一棵线段树,每个叶子节点对应一个i(0到n-2),存储该i的
- 查询阶段:
- 把查询的i范围
[a, b-1]拆成线段树里的O(log n)个不重叠节点(这是线段树的常规操作)。 - 对每个拆分出来的节点:
- 用二分查找在
sorted_L里找到最大的索引j,使得sorted_L[j] ≤ k——如果找不到,说明这个节点里没有符合条件的i,直接跳过。 - 拿这个j值,去查对应的
sorted_R前缀的排序数组,用二分找到第一个≥k的元素位置,用j+1减去这个位置,就是这个节点里符合条件的i的数量。
- 用二分查找在
- 把所有节点的统计结果加起来,就是最终答案。
- 把查询的i范围
- 复杂度说明:预处理时间是O(n (log n)²),每次查询时间是O((log n)²),完全符合亚线性的要求。
方案2:二维线段树(适合k的取值范围很大的场景)
如果k的取值范围特别大(比如A里的元素是1e9级别的),可以先用坐标压缩把所有涉及的k值(包括所有L_i、R_i和查询的k)映射到一个连续的小范围,然后用二维线段树来处理:
- 预处理阶段:
- 建一棵外层线段树,每个节点对应一段k的范围。外层的每个节点内部,再建一棵内层线段树,内层线段树的节点对应i的位置(0到n-2),支持单点加1和区间求和操作。
- 对每个i,我们要在外层线段树的
[L_i, R_i]区间内,给内层线段树的i位置加1——这相当于给所有覆盖[L_i, R_i]的外层节点,都执行一次内层线段树的单点更新。
- 查询阶段:
- 在外层线段树里找到对应k值的节点(或者拆分出覆盖k的几个节点)。
- 在这些节点的内层线段树里,查询
[a, b-1]区间的总和,这个总和就是符合条件的i的数量。
- 复杂度说明:预处理时间是O(n log M log n)(M是压缩后的k范围),查询时间是O(log M log n),也是亚线性的。
方案3:离线查询+前缀和(适合批量查询场景)
如果所有查询都是提前已知的(离线场景),这个方案会更简单高效:
- 预处理+查询阶段:
- 收集所有的查询,还有所有i对应的
[L_i, R_i],把所有涉及的k值(L_i、R_i、查询的k)都收集起来,排序去重,给每个k分配一个压缩后的索引(这样能把大的k范围缩小)。 - 对每个i,生成两个事件:当k等于
L_i时,给i位置加1;当k等于R_i + 1时,给i位置减1。 - 把所有事件按k值从小到大排序,同时把所有查询也按k值从小到大排序。
- 维护一个前缀和数组
prefix(初始全0),然后按k的顺序依次处理事件和查询:- 先处理当前k对应的所有事件:如果是加1事件,就给
prefix[i] +=1;如果是减1事件,就给prefix[i] -=1。 - 再处理当前k对应的所有查询:计算
prefix[b-1] - (prefix[a-1] if a>0 else 0),这个结果就是该查询的答案。
- 先处理当前k对应的所有事件:如果是加1事件,就给
- 收集所有的查询,还有所有i对应的
- 复杂度说明:整体时间是O((n + Q) log(n+Q))(Q是查询数量),每个查询的处理是O(1),批量处理效率很高,也是亚线性的。
用示例验证一下
拿你给的示例:数组是[6,3,2,8,5],a=1,b=3,k=3。
- 相邻元素对的i范围是0-3:
- i=0:L=3,R=6
- i=1:L=2,R=3
- i=2:L=2,R=8
- i=3:L=5,R=8
- 查询的i范围是1-2,用方案1的话,线段树里覆盖i=1-2的节点的
sorted_L是[2,2],sorted_R是[3,8]。查询时,二分sorted_L找到≤3的最大j是1,然后在前2个sorted_R元素里统计≥3的数量,两个都符合,所以结果是2,和示例一致。
内容的提问来源于stack exchange,提问作者pokemon
相关产品推荐
相关产品推荐

