优化:将数组划分为长度不大于k的连续子序列,使每个子序列的最大值之和最小

时间:2016-12-23 13:09:33

标签: algorithm dynamic-programming

O(n^2)算法优化为O(n log n)

问题陈述

给定A个正整数的数组n。将数组划分为长度不大于k的连续子序列,使得每个子序列的最大值之和最小。这是一个例子。

如果n = 8k = 5以及数组元素为1 4 1 3 4 7 2 2,则最佳解决方案为1 | 4 1 3 4 7 | 2 2。总和为max{1} + max{4, 1, 3, 4, 7} + max{2, 2} = 1 + 7 + 2 = 10

O(n ^ 2)解决方案

dp[i]成为子问题数组A[0] ... A[i]的问题陈述中的最小和。 dp[0] = A[0]0 < i < ndp[-1] = 0),

dp[i] = min(0, i-k+1 <= j <= i)(dp[j - 1] + max{A[j], ..., A[i]})

// A, n, k, - defined
// dp - all initialized to INF
dp[0] = A[0];
for (auto i = 1; i < n; i++) {
    auto max = -INF;
    for (auto j = i; j >= 0 && j >= i-k+1; j--) {
        if (A[j] > max)
            max = A[j];
        auto sum = max + (j > 0 ? dp[j-1] : 0);
        if (sum < dp[i])
            dp[i] = sum;
    }
}
// answer: dp[n-1]

O(n log n)?

问题作者声称可以在O(n log n)时间内解决这个问题,并且有些人能够通过测试用例。如何优化?

1 个答案:

答案 0 :(得分:3)

注意:我会略微改变您的动态编程关系,因此j = 0没有特殊情况。现在dp[j]是第一个jA[0], ..., A[j-1]

的答案

dp[i] = min(dp[j] + max(A[j], ..., A[i-1]), i-k <= j < i)

问题的答案现在是dp[n]

请注意,如果j < idp[j] >= dp[i],您在以下转换中不需要dp[j],因为max(A[j], ..., A[l]) >= max(A[i], ..., A[l])(因此,切换到{时总是更好{1}}代替i

此外,让j(其中C[j] = max(A[j+1], ..., A[l])是动态编程步骤中的当前索引,即C ++程序中的l

然后你可以在内存中保留一些索引i(动态编程关系转换的“有趣”索引),这样:x1 < ... < xm(1)。然后自动dp[x1] < ... < dp[xm](2)。

要存储C[x1] >= ... >= C[xm],我们需要一些支持以下操作的数据结构:

  • 弹回(当我们从{x1, ..., xm}移动到i时,我们必须说i+1现在无法访问)或前面(参见插入)。
  • 推前i-k(当我们计算x时,我们在保留(1)的同时插入它,删除相应的元素。)
  • 计算dp[i]

因此,一些将min(dp[xj] + C[xj], 1 <= j <= m)x1, ..., xk一起存储以存储所有set的队列就足够了。

当我们插入元素dp[xi] + C[xi]时,我们如何保留(1)和更新C

  • 在计算i之前,我们使用dp[i]更新C。为此,我们在集合A[i-1] s.t中找到最小元素xjx。然后(1)和(2)暗示所有C[xj] <= A[i-1] dp[j'] + C[j'] >= dp[j] + C[j],我们将j' >= j更新为C[xj],然后从集合中删除A[i-1](*)
  • 当我们插入x(j+1), ..., xm时,我们只删除所有元素s.t.弹出前面dp[i]
  • 当我们删除dp[j] >= dp[i]时,(*)中被破坏的某个元素现在可能变得最好。因此,如有必要,我们会更新i-k并插入最后一个元素。

复杂性:C(集合中最多可插入O(n log n)个。)

此代码总结了主要观点:

2n

这次我对你的算法进行了压力测试:see here

如果目前还不清楚,请告诉我。

相关问题