《(蒲公英)众数查询中块数选择的优化:平衡预处理与查询时间》

《(蒲公英)众数查询中块数选择的优化:平衡预处理与查询时间》

在分块算法中,块大小(或块数)的选择核心是平衡预处理时间和查询时间,使得整体复杂度最优。对于本题(众数查询问题),将块数 t 设为 sqrt(Q * log2(n)),本质是通过数学推导让预处理和查询的时间复杂度尽可能接近,从而最小化总耗时。

关键:明确预处理与查询的时间表达式

要理解块数的选择,需先拆解两部分核心耗时:

1. 预处理时间

pre(i) 函数的作用是:对第 i 个块,从块的起始位置 L[i] 遍历到数组末尾,记录每个位置对应的 “当前众数”,存储在 mo 数组中。

  • 单次 pre(i) 的时间复杂度为 O(n)(遍历从 L[i]n,共 O(n) 步)。

  • 总共有 t 个块,因此预处理总时间为 O(t * n)

2. 查询时间

每次查询 qes(l, r) 需要处理三部分:左边角块、右边角块、中间完整块。

  • 中间完整块可直接用预处理的 mo 数组,时间 O(1)

  • 边角块的处理:每个边角块的长度最多为 “块大小 s”(s = n/t,因为总块数为 t,总长度 n,故每块约 n/t 个元素)。

  • 对边角块的每个元素,需用 get 函数(二分查找)计算其在 [l, r] 中的出现次数,单次二分时间 O(log n)。因此,单次查询处理边角块的时间为 O(s * log n)(边角块总长度 O(s))。

  • 总共有 Q 次查询,因此总查询时间为 O(Q * s * log n)

目标:平衡预处理与查询时间

块大小 s = n/t(因为总块数 t,总长度 n),代入查询时间表达式得:

总查询时间 = O(Q * (n/t) * log n)

预处理时间 = O(t * n)

为了让整体复杂度最优,需让预处理时间与查询时间 “数量级相当”(即两者平衡),即:

t * n ≈ Q * (n/t) * log n

约去 n 后化简:

t² ≈ Q * log n

因此:

t ≈ sqrt(Q * log n)

为什么这样设置更优?

若选择传统的 t = sqrt(n)(块大小 s = sqrt(n)):

  • 预处理时间 = O(sqrt(n) * n) = O(n^(3/2))

  • 查询时间 = O(Q * sqrt(n) * log n)

Q 较大(如 Q ~ n)时,查询时间会远大于预处理时间(O(n * sqrt(n) * log n) 远大于 O(n^(3/2))),整体复杂度由查询主导,效率较低。

而选择 t = sqrt(Q * log n) 时:

  • 预处理时间 = O(sqrt(Q * log n) * n)

  • 查询时间 = O(Q * (n / sqrt(Q * log n)) * log n) = O(n * sqrt(Q * log n))

两者数量级相同,整体复杂度达到最优平衡,尤其在 Q 较大时优势明显。

总结

块数 t = sqrt(Q * log2(n)) 的设置,是通过数学推导让预处理时间与查询时间平衡,从而最小化整体复杂度,这是针对 “众数查询” 问题(查询次数多、单次查询处理边角块耗时)的优化选择。

posted @ 2025-07-14 20:36  liduo  阅读(8)  评论(0)    收藏  举报