简体   繁体   English

将给定的伪代码复杂度从O(N ^ 4)减少到O(NlogN)或更小

[英]Reduce given pseudocode complexity from O(N^4) to O(NlogN) or lesser

I recently came across this HackerRank contest problem ( Coolguy and Two Subsequences ). 我最近遇到了这个HackerRank竞赛问题( Coolguy和Two Subsequences )。 The question is to reduce the given pseudocode complexity from O ( N 4 ) to something much lesser. 问题是将给定的伪代码复杂度从ON 4 )减少到更小的值。

//f(a, b) is a function that returns the minimum array element in interval [a, b]

array = [...] (of length n)

ans = 0

for a -> [1, n]
    for b -> [a, n]
        for c -> [b + 1, n]
            for d -> [c, n]
                ans = ans + min(f(a, b), f(c, d))

Given editorial solution achieves a complexity of O ( N log N ). 鉴于编辑解决方案实现了ON log N )的复杂性。 I found the problem an interesting concept to learn. 我发现问题是一个值得学习的有趣概念。 Unfortunately, I had difficulty in understanding the editorial solution which uses segment trees to store sub solutions in a given range and finally merge it give the final solution. 不幸的是,我很难理解编辑解决方案,它使用段树来存储给定范围内的子解决方案,最后合并它给出最终解决方案。 Specifically I did not understand the significance of each term in the segment tree. 具体来说,我不理解段树中每个术语的重要性。

I understand that the question has alternative solutions too. 我知道这个问题也有替代解决方案。 Can someone please explain an approach to do the question. 有人可以解释一下做问题的方法。 Code is not required but you can add it if it helps you in explaining the solution better. 代码不是必需的,但如果它可以帮助您更好地解释解决方案,则可以添加代码。

Without looking at the editorial solution, here's a path to an O(N log N)-time solution. 在没有查看编辑解决方案的情况下,这是一个O(N log N)时间解决方案的路径。

Let's say that we have an input like 让我们说我们有一个输入

[3, 1, 4, 5, 9, 2, 6].

Delete the numbers from least to greatest, tracking sublists that were originally contiguous with a binary tree. 删除从最小到最大的数字,跟踪最初与二叉树连续的子列表。

A [3, 1, 4, 5, 9, 2, 6]
B [3]   [4, 5, 9, 2, 6]
C [3]   [4, 5, 9]   [6]
D       [4, 5, 9]   [6]
E          [5, 9]   [6]
F             [9]   [6]
G             [9].

At each step, we count the number of loop iterations that use only remaining numbers (call these A, B, C, D, E, F, G ). 在每一步,我们计算仅使用剩余数字的循环迭代次数(称为A, B, C, D, E, F, G )。 The answer is 答案是

1 (A - B) + 2 (B - C) + 3 (C - D) + 4 (D - E) + 5 (E - F) + 6 (F - G) + 9 G.

The count at each step is a symmetric polynomial in the lengths of the originally contiguous sublists. 每个步骤的计数是原始连续子列表的长度中的对称多项式。

A [7]
B [1, 5]
C [1, 3, 1]
D [3, 1]
F [2, 1]
G [1, 1]
H [1]

This symmetric polynomial can be expressed in a constant number of operations by adding and multiplying power sums, eg, for C , those would be 这种对称多项式可以通过增加和乘以功率和来表示为恒定数量的运算,例如,对于C ,那些将是

0: 1^0 + 3^0 + 1^0
1: 1^1 + 3^1 + 1^1
2: 1^2 + 3^2 + 1^2.

We can keep the relevant power sums up to date as we delete. 我们删除时,我们可以保持相关的功率总和是最新的。 When we replace 7 with 1, 5 , we subtract 7^2 from the sum of squares and add 1^2 + 5^2 . 当我们用1, 5替换7时,我们从平方和中减去7^2并加上1^2 + 5^2

Something like this in C++ (untested): C ++(未经测试)中有类似的东西:

#include <algorithm>
#include <iostream>
#include <set>
#include <utility>
#include <vector>

int main(void) {
  int n;
  if (!(std::cin >> n) || n < 1 || n > 200000) {
    return 1;
  }
  std::vector<std::pair<int, int>> a(n);
  for (int i = 0; i < n; i++) {
    int a_i;
    if (!(std::cin >> a_i)) {
      return 1;
    }
    a[i] = std::make_pair(a_i, i);
  }
  std::sort(a.begin(), a.end());
  std::set<int> endpoints = {-1, n};
  int sum1 = n;
  int sum2 = n * n;
  int sum3 = n * n * n;
  int sum4 = n * n * n * n;
  int previous_count = (n + 2) * (n + 1) * n * (n - 1) / 24;
  int answer = 0;
  for (const auto &p : a) {
    const int a_i = p.first;
    const int i = p.second;
    const auto it = endpoints.insert(i).first;
    auto left = it;
    --left;
    auto right = it;
    ++right;
    const int minus = *right - (*left + 1);
    sum1 -= minus;
    sum2 -= minus * minus;
    sum3 -= minus * minus * minus;
    sum4 -= minus * minus * minus * minus;
    const int plus_left = *right - (i + 1);
    sum1 += plus_left;
    sum2 += plus_left * plus_left;
    sum3 += plus_left * plus_left * plus_left;
    sum4 += plus_left * plus_left * plus_left * plus_left;
    const int plus_right = i - (*left + 1);
    sum1 += plus_right;
    sum2 += plus_right * plus_right;
    sum3 += plus_right * plus_right * plus_right;
    sum4 += plus_right * plus_right * plus_right * plus_right;
    const int count =
        ((sum2 + sum1) * (sum2 + sum1) - (sum4 + 2 * sum3 + sum2)) / 8 +
        (sum4 + 2 * sum3 - sum2 - 2 * sum1) / 24;
    answer += (previous_count - count) * a_i;
    previous_count = count;
  }
  std::cout << answer << '\n';
  return 0;
}

Additional details(for anyone interested) regarding derivation of "count" in any iteration(number of loop iterations that use only remaining numbers): 关于在任何迭代中推导“计数”的其他细节(对任何感兴趣的人)(仅使用剩余数字的循环迭代次数):

Following from code posted in David Eisenstat's answer: 以下代码发布在David Eisenstat的回答中:

Consider following originally contiguous sub lists: C => [... X .. Y .. Z ...] 考虑遵循最初连续的子列表:C => [... X .. Y .. Z ...]

Selection of indices of form [a,b][c,d] can be obtained from at most 2(any) sub lists. 形式[a,b] [c,d]的索引的选择可以从最多2个(任何)子列表中获得。

Let, X(i) = Number of ways of selecting i indices from contiguous sub list X = x(C)i (x= length of X) 设,X(i)=从连续子列表中选择i索引的方式数X = x(C)i(x = X的长度)

Total ways of doing this from any two sub lists X,Y:
X(2)*Y(2) =>     [a,b] selected from sublist X, [c,d] selected from sublist Y.
X(4)+Y(4) =>     [a,b][c,d] selected from sublist X and Y.
X(1)*Y(2) + X(2)*Y(1)    =>      [a,a] selected from X, [c,d] selected from Y. [a,b] selected from X,[c,c] selected from Y.
2*(X(3)+Y(3))            =>      [a,a][b,c]/[a,b][c,c] selected from X and Y.
X(1)*Y(1)                =>     [a,a] selected from X and [b,b] selected from Y.    
X(2) + Y(2)              =>     [a,a][b,b] selected from X and Y.   

Total ways of selection("count", mentioned in program): 总选择方式(“计数”,在计划中提到):

(i) X(2)*Y(2) + X(1)*Y(2) + X(2)*Y(1); (i)X(2)* Y(2)+ X(1)* Y(2)+ X(2)* Y(1); For all sub list pairs X,Y in list C. 对于列表C中的所有子列表对X,Y。

+ +

(ii) X(2) + 2*X(3) + X(4) ; (ii)X(2)+ 2 * X(3)+ X(4); For all sub lists X in C. 对于C中的所有子列表X

Due to symmetric nature of polynomials obtained, 由于获得的多项式的对称性,

(i) X(2)*Y(2)  + X(1)*Y(2) + X(2)*Y(1) = [(sum1+sum2)*(sum1+sum2)-(sum1+2*sum3+sum4)]/8

(ii) X(2) + 2*X(3) + X(4) = [sum4+2*sum3-sum2-2*sum1]/24

Note: sum1, sum2, sum3, sum4 as defined by program. 注意:sum1,sum2,sum3,sum4由程序定义。

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

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