计算范围内的反转

时间:2014-02-13 19:11:04

标签: algorithm data-structures

我参加了一个我无法解决问题的编程竞赛,问题是:

给定n个整数的数组A,我需要计算给定范围内的反转次数。 提供一个整数m,它表示范围的数量,然后是m行,在每一行中给出两个整数li和ri。

我们必须仅计算指定范围内的反转,即从li到ri(0基于索引)。

如果A[i]>A[j]i<j,则有两个元素A [i]和A [j]添加到反转中。

例如:A=[3 2 1 4]

反转是:

(2, 1), (3, 1), (3, 2) i.e. total number of inversions are 3.

输入:

3 2 1 4    //Array A 
3         // m - no. of ranges
1 2      // range
2 3
0 3

输出:

1
0
3

约束:

n<=2*10^4
m<=2*10^4
A[i]<=10^9

我知道在整个数组中计算O(nlogn)中的反转计数(例如BIT或合并排序)的方法,如果我在这里对每个范围应用相同的复杂度将是O(mnlogn),那肯定是不可接受的时间限制是1秒。

4 个答案:

答案 0 :(得分:3)

这是一个O((n + m)sqrt n log n)-time算法。这可能还不够好,但有些事情似乎并不恰到好处 - 通常的编程竞赛技巧都行不通。 (O((n + m)sqrt n)可以更加小心地实现。)

将输入数组划分为长度为sqrt n的sqrt n个子数组,称为 blocks 。使用增量算法计算反转,对于由数组和数组前缀组成的每一对,计算第一个元素来自前者而第二个元素来自后者的反转次数。 (O(n sqrt n log n))对前缀 - 块对执行相同的操作。

对于每个输入范围,将其分解为某些块(阻塞元素)和少于2个sqrt n个元素(未阻塞元素)的并集。使用预先计算的结果和包含 - 排除,找到至少阻止一个元素的范围内的反转次数。 (O(sqrt n))计算并向该数量添加涉及两个未阻塞元素的范围内的反转次数。 (O(sqrt n log n))

答案 1 :(得分:2)

O((n + m)* sqrt(n)* log(n))时间,具有离线查询的O(n + m)空间算法(必须知道所有查询范围)提前):

  1. 将数组A划分为大约sqrt(n)个相等的部分。
  2. 对于阵列的每个部分,执行步骤3 ... 7。
  3. 初始化3个指针(PbeginPmidPend),使它们都指向数组当前部分的结尾(或者更好地指向属于这部分)。
  4. 提前Pend;更新订单统计树,用于确定PmidPend之间的反转次数;当Pend与在数组的当前部分开始的某个范围的结尾重合时,请执行步骤5 ... 7.
  5. 向后移动Pbegin,直到它与步骤4中找到的范围的开头一致。累计订单统计树中的元素数量少于Pbegin指向的值(但不更新树)
  6. 查找PbeginPmid之间的反转次数(使用合并排序或单独的订单统计树)。
  7. 将步骤4,5,6中的反转次数加在一起。这是当前范围的反转次数。
  8. 这里可以使用BIT / Fenwick树作为订单统计树的有效实现。在这种情况下,需要进行一些预处理:在数组的排序副本中用索引替换数组值以压缩值范围。


    O((n + m)* sqrt(n))时间,O(n * sqrt(n))空间算法,带有在线查询。正如大卫艾森斯塔特所暗示的那样。

    预处理:

    1. 通过数组的排序副本中的索引替换数组值以压缩值范围。
    2. 将数组A划分为大约sqrt(n)个相等的部分。
    3. 对于数组的每个部分B,执行步骤4 ... 8。
    4. 零初始化大小为'n'的位集。切换对应于部分'B'的值的位。对于此前缀加总计P和后缀数组S的bitset计算数组。
    5. 对于C部分之前的B部分执行第6步。
    6. 从部分v的上一个值C开始向后,将所有值P[v]加在一起,并将sqrt(n)结果写入查找表E。该步骤的复杂度为O(n * sqrt(n))。
    7. 对于D部分B后面的每个部分执行第8步。
    8. 从部分v的第一个值D开始并向前,将所有值S[v]加在一起,并将sqrt(n)结果写入查找表F。该步骤的复杂度为O(n * sqrt(n))。
    9. 使用增量算法(Fenwick树)计算所有块后缀中的反转次数(从块边界开始的所有子数组的长度不大于sqrt(n))。将结果写入查找表G。该步骤的复杂性为O(n * log n)。
    10. 使用增量算法计算所有块前缀中的反转次数。将结果写入查找表H。该步骤的复杂性为O(n * log n)。
    11. 使用EF以及GH中的任何一个来计算连续块中的反转次数(使用任意一对块来开始和结束位置)。将结果写入查找表R。这一步的复杂性是O(n)。
    12. 在预处理之后,我们有几个LUT包含块前缀/后缀(GH,O(n)空间)中的反转次数,完整块之间的反转次数和块前缀/后缀(EF,O(n * sqrt(n))空间),以及连续块(R,O(n)空间)中的反转次数。 (可选地,我们可以将EFR合并,这会增加预处理时间,但允许第一个查询步骤的O(1)时间。)

      查询:

      1. 使用R获取连续完整块中的反转次数,使用EF添加前缀/后缀与每个完整块之间的反转次数,使用{{ 1}}和G添加前缀和后缀中的反转次数。
      2. 要获得前缀和后缀之间的反转次数,请使用基数排序(H计数器,2次计数传递和2次传递来分配值)进行排序,然后合并它们。
      3. 添加步骤1和2中的值以获取查询范围的反转次数。

答案 2 :(得分:1)

第3范围:索引0 - 3包含第1和第2范围。

如果您知道先前范围中包含的反转次数,则跳过它们。因此,在第三个范围内,您可以跳过比较1到2并跳过比较2到3。

所以,在第3个范围内你只比较,

 0 -> 1
 0 -> 2
 0 -> 3
 1 -> 3

这是最佳情况O(nlogn)和最坏情况O(mnlogn)。

答案 3 :(得分:1)

以下是对前一个答案的详细说明,也是填补了潜在的差距。首先,在O(n log n)时间内,通过从右到左一次添加一个元素并进行二叉搜索树搜索来查找树中的元素,计算并存储数组所有前缀的反转次数。所有先前的元素确定添加的额外反转次数,然后将该元素插入二叉树(并将树维护为自平衡二叉搜索树)。然后,您同样计算并存储所有后缀中的反转次数。然后,如果要计算范围[L,R]中的反转次数,则将从L开始的前缀的反转添加到以R结尾的后缀中的反转,并减去整个反转的总反转次数。阵列。这几乎给出了答案,但并不完全,因为它给出了答案减去范围[1,L-1]和[R + 1,n]之间的反转次数。因此,您需要能够计算数组中任意前缀和后缀对之间的反转次数。为此,您计算任意前缀与以sqrt(n)的倍数开头的特定后缀之间的反转次数。您可以在O(n ^(3/2)log n)时间内对每个后缀进行排序,然后对于每个后缀,从左到右添加一个元素到前缀,在后缀中进行二进制搜索以找出倒数的数量加多少。类似地,您计算并存储每个前缀之间的反转次数,这些前缀以sqrt(n)的倍数结尾,并且每个元素在O(n ^(3/2)log n)时间内的前缀右侧。

然后,对于给定的范围,您取前缀和后缀并将后缀四舍五入以最接近的sqrt(n)的倍数结束,并在O(1)时间内查找反转的数量。然后取出后缀中的其余元素,并在O(sqrt(n))时间内查找前缀中以最接近的sqrt(n)lower的倍数结束的反转次数。然后你获取后缀中的其余元素和前缀中的其余元素(不包括在sqrt(n)端点中),并且暴力计算O(sqrt(n)log n)时间内它们之间的反转次数。总计算时间为每个范围的O(sqrt(n)log n),给出O((m + n)sqrt(n)log n)时间的总运行时间,该时间应满足1秒的时间限制。