繁体   English   中英

范围内的Kth最小值

[英]Kth minimum in a Range

给定一个整数数组和一些查询操作。
查询操作有两种类型
1.将第i个索引的值更新为x。
2.给2个整数找到该范围内的第k个最小值。(例如,如果2个整数是i和j,我们必须找出i和j之间的第k个最小值)。
我可以使用分段树找到范围最小查询但是对于第k个最小值不能这样做。 谁能帮我?

这是一个O(polylog n)每个查询解决方案实际上不假设常数k ,因此k可以在查询之间变化。 主要思想是使用一个分段树,其中每个节点代表一个数组索引的间隔,并包含一个代表数组段中值的多集(平衡二叉搜索树)。 更新操作非常简单:

  1. 从叶子(您正在更新的数组索引)向上走分段树。 您将遇到表示包含更新索引的数组索引间隔的所有节点。 在每个节点上,从多集中删除旧值并将新值插入到多集中。 复杂性: O(log^2 n)
  2. 更新阵列本身。

我们注意到每个数组元素都在O(log n)集合中,因此总空间使用量为O(n log n) 通过多个集合的线性时间合并,我们也可以在O(n log n)构建初始段树O(n log n)每个级别有O(n)工作)。

查询怎么样? 给出范围[i, j]和秩k并且想要找到a[i..j]a[i..j]第k个最小元素。 我们怎么做?

  1. 使用标准段树查询过程查找查询范围的不相交的覆盖范围。 我们得到O(log n)不相交的节点,其多重集合的联合正是查询范围中多个值的集合。 让我们称那些多重集合s_1, ..., s_mm <= ceil(log_2 n) )。 查找s_i需要O(log n)时间。
  2. s_1, ..., s_m的并集执行select(k)查询。 见下文。

那么选择算法如何工作呢? 有一个非常简单的算法可以做到这一点。

我们有s_1, ..., s_nk给想要找最小的xa ,这样s_1.rank(x) + ... + s_m.rank(x) >= k - 1 ,其中rank回报相应BBST中小于x的元素数量O(log n)如果我们存储子树大小,则可以在O(log n)实现)。 我们只是使用二进制搜索来找到x 我们遍历根的BBST,做几个排名查询并检查它们的总和是否大于或等于k 它是x的谓词单调,因此二进制搜索有效。 答案是任何s_ix的后继者的最小值。

复杂性 :每次查询O(n log n)预处理和O(log^3 n)

因此,对于q查询,我们总共得到O(n log n + q log^3 n)的运行时。 我确信我们可以通过更聪明的选择算法将其降低到O(q log^2 n)

更新:如果我们正在寻找可以一次处理所有查询的离线算法,我们可以使用以下算法得到O((n + q) * log n * log (q + n))

  • 预处理所有查询,创建一组在数组中发生的所有值。 这些数量最多为q + n
  • 构建一个分段树,但这次不在数组上,而是在可能的值集上。
  • 段树中的每个节点都表示值的间隔,并维护一组出现这些值的位置。
  • 要回答查询,请从段树的根开始。 检查根的左子节点中有多少位置位于查询间隔中(我们可以通过在位置的BBST中进行两次搜索来实现)。 让那个数字是m 如果k <= m ,则递归到左边的孩子身上。 否则递归到正确的孩子, k减少m
  • 对于更新,从O(log (q + n))节点中移除覆盖旧值的位置,并将其插入到覆盖新值的节点中。

这种方法的优点是我们不需要子树大小,因此我们可以使用平衡二叉搜索树的大多数标准库实现(例如,在C ++中set<int> )来实现这一点。

我们可以通过将片段树更改为权重平衡树(例如BB [α]树)来将其转换为在线算法。 它具有与其他平衡二叉搜索树相同的对数运算,但允许我们通过将重建成本计入必然导致不平衡的操作来重建不平衡时从头开始重建整个子树。

如果这是编程竞赛问题,那么您可能能够使用以下O(n log(n)+ qn ^ 0.5 log(n)^ 1.5)-time算法。 它被设置为使用C ++ STL并且具有比Niklas(之前的?)答案更好的大O常数,因为它使用了更少的空间和间接。

将数组划分为长度为n / k的k个块。 将每个块复制到第二个阵列的相应位置并对其进行排序。 要更新:将更改的块复制到第二个数组并再次排序(时间O((n / k)log(n / k))。要查询:复制到临时数组最多2(n / k - 1)属于与查询间隔部分重叠的块的元素。对它们进行排序。使用此问题的答案之一从时间O中选择已排序的临时数组和完全重叠的块的并集中所请求的排名的元素(k log(n / k)^ 2)。理论上k的最优设置是(n / log(n))^ 0.5。使用Frederickson和Johnson的复杂算法可以削减另一个log(n)^ 0.5 。

执行存储桶排序的修改:创建一个包含所需范围内的数字的存储桶,然后仅对该存储桶进行排序并找到第k个最小值。

该死的,这个解决方案无法更新元素但至少找到了第k个元素,在这里你会得到一些想法,这样你就可以想到一些提供更新的解决方案。 尝试基于指针的B树。

这是O(n log n)空间和O(q log ^ 2 n)时间复杂度。 后来我用每个查询的O(log n)解释了相同的内容。

所以,你需要做下一个:

1)在给定数组上创建“分段树”。

2)对于每个节点,您将存储整个数组,而不是存储一个数字。 该数组的大小必须等于它的子数。 该数组(如您所猜测的)必须包含底部节点(子节点或该节点中的数字)的值,但已排序。

3)要制作这样的数组,你可以从它的两个儿子合并来自分段树的两个数组。 但不仅如此,对于你刚刚制作的数组中的每个元素(通过合并),你需要记住数字在合并数组中插入之前的位置(基本上,它来自的数组,并在其中定位) 。 以及指向未从同一数组插入的第一个下一个元素的指针。

4)使用此结构,您可以在某些段S中检查有多少数量低于给定值x的数字。您可以找到(使用二进制搜索)根节点数组中的第一个数字> = x。 然后,使用您所做的指针,您可以在O(1)中找到两个子数组(作为前一个节点的子节点的节点数组)的相同问题的结果。 您停止为每个节点操作此降序,该节点表示在给定段S内部或​​外部的整个段。时间复杂度为O(log n):O(log n)以查找> = x的第一个元素,和O(log n)的所有S分解段。

5)对解决方案进行二进制搜索。

这是每个查询具有O(log ^ 2 n)的解决方案。 但是你可以减少到O(log n)

1)在完成上面所写的所有操作之前,您需要转换问题。 您需要对所有数字进行排序并记住原始数组中每个数字的位置。 现在这些位置代表您正在处理的阵列。 调用该数组P.

如果查询段的边界是a和b。 你需要找到P中的第k个元素,它是a和b之间的值(而不是索引)。 该元素表示原始数组中结果的索引。

2)为了找到第k个元素,你会做一些复杂度为O(log n)的反向跟踪。 您将询问索引0和(某些其他索引)之间的元素数量,这些元素在a和b之间除以值。

3)假设你知道某个段(0,h)的这个问题的答案。 从最大的一个开始,为从h开始的树中的所有段获得相同类型问题的答案。 只要当前答案(来自段(0,h))加上你最后得到的答案大于k,就继续得到那些答案。 然后更新h。 继续更新h,直到树中只有一个以h开头的段。 那个h是你在所述问题中寻找的数字的索引。

要从树中获得某个段的问题的答案,您将花费恰好O(1)的时间。 因为你已经知道它的父节点的答案,并且使用我在第一个算法中解释的指针,你可以得到O(1)中当前段的答案。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM