【发布时间】:2018-11-10 10:03:06
【问题描述】:
我对具有 n 个元素的数组 A 有更新和查询。
我有两种查询:
类型 1(更新):给定 x,递减数组 A 中值大于或等于 x 的所有元素的值。
type 2 (Query) : 给定 (l, r, x),找出数组 A 中第 x 个最小的元素,其值介于 l 和 r(均包含)之间。
我想不出比蛮力更好的解决方案,它可能与 O(q*n) 一样昂贵。有没有最佳解决方案? 数组 A 的元素最大可达 10^18。
【问题讨论】:
我对具有 n 个元素的数组 A 有更新和查询。
我有两种查询:
类型 1(更新):给定 x,递减数组 A 中值大于或等于 x 的所有元素的值。
type 2 (Query) : 给定 (l, r, x),找出数组 A 中第 x 个最小的元素,其值介于 l 和 r(均包含)之间。
我想不出比蛮力更好的解决方案,它可能与 O(q*n) 一样昂贵。有没有最佳解决方案? 数组 A 的元素最大可达 10^18。
【问题讨论】:
我正在考虑的数据结构是自平衡二叉树的增强变体,例如 AA 树。除了普通成员之外,每个节点还存储了一个递减计数,这些递减计数隐式应用于其后代。与其更改数字本身,不如从每个祖先节点中减去递减计数来查看节点的最终值。每个节点还维护其后代的计数。
更新包括对树的绑定搜索(复杂度O(log n)),并增加O(log n) 节点的递减计数。在某些情况下,您还必须将边界下方的元素移动到该树中(因为它们不再小于所有元素,这要归功于递减),这也是O(log n)。 (请注意,最后一点需要进行一些棘手的后代更新,但我认为这不会超出总体界限。)
“查询”操作很简单;二进制搜索找到l(r 与操作无关,AFAICT)然后使用后代计数加速找到该范围内的第 k 个最小到 O(log k)。
所以这两个操作都有时间O(log n),总时间是O(q*log n),假设你不计算O(n*log n)时间来构建初始树。
【讨论】:
没有比O(n) 更快地运行每个描述的查询更好的解决方案,其中 n 是数组元素的数量,在您描述问题时始终在未排序的数组上。那是因为您必须为查询考虑数组中的每个元素。问题是您没有关于您的数据的信息,因此您永远不知道在哪里检查以使事情变得更快。
如果您要进行大量查询,您可以通过对数组进行排序并保持排序来进行一些优化。虽然排序最初将采用O(n log(n))(快速排序、合并排序),但它允许您使用修改后的二分搜索(查找范围内的第一个最小元素,然后取下 x 个元素)。 (如果你使用像 qsort 这样的库排序,你会以 1.3 nlog(n) 之类的方式排序,这非常好)
它还可以让您更快地执行更新,因为您可以再次使用 BSearch 来获取您正在寻找的值。在这里,有关数据的知识很重要。如果你的减量不会改变顺序很多(检查边界案例元素),那么从长远来看这更有效。
如果您的数组已排序,您还可以通过仅考虑第一个元素左侧的元素大于或等于 x 来有效地在 Q1 之后进行排序。如果这些元素中的一些现在比您的一些减少的元素大,您可以找到它们之间的边界并在第一季度之后在较小和较大区域之间进行交换(智能排序,使用您在数据上的信息)。
如果您不能对数据进行排序(出于绘图目的等),那么线性时间是您所希望的最佳选择。如果您的数组很小,也不需要排序,这将是矫枉过正,不会带来明显的改进。老实说,线性时间在现代计算机上真的很好用。
【讨论】:
b,左边的一个(下一个最小的)a。现在如果b在减量后小于a,则找到b之后的下一个元素(命名为c,使用二分查找),它大于a。现在您将a 移动到c 之前,然后重复(总是找到新的c)直到b >= a。在最坏的情况下,这将是 O(m*log(m)),其中 m 是更新子数组的大小。