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

aqu*_*z11 11 algorithm dynamic-programming

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

问题陈述

考虑到阵列An正整数.将数组划分为长度不大于连续子序列,k使得每个子序列的最大值之和最小.这是一个例子.

如果n = 8和,k = 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 < n(dp[-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]
Run Code Online (Sandbox Code Playgroud)

O(n log n)?

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

md5*_*md5 3

注意:我将稍微改变你的动态编程关系,这样就不会有特殊情况 if 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])(所以最好切入i而不是j.

此外让C[j] = max(A[j+1], ..., A[l])l动态编程步骤中我们当前的索引在哪里,即i在您的 C++ 程序中)。

然后,您可以在内存中保留一组索引x1 < ... < xm(动态规划关系转换的“有趣”索引),例如:dp[x1] < ... < dp[xm](1)。然后自动C[x1] >= ... >= C[xm](2)。

为了存储{x1, ..., xm},我们需要一些支持以下操作的数据结构:

  • 向后弹出(当我们从 移动i到时i+1,我们必须说i-k现在无法访问)或向前(参见插入)。
  • 推前x(当我们计算出 时dp[i],我们通过删除相应的元素来插入它,同时保留 (1))。
  • 计算min(dp[xj] + C[xj], 1 <= j <= m).

因此,一些要存储的队列x1, ..., xk和一个set要存储所有的队列dp[xi] + C[xi]就足够了。


C当我们插入一个元素时,我们如何保留(1)并更新i

  • 在计算之前dp[i],我们C用 进行更新A[i-1]。为此,我们找到xj集合xst中的最小元素C[xj] <= A[i-1]。那么 (1) 和 (2) 意味着dp[j'] + C[j'] >= dp[j] + C[j]all j' >= j,因此我们更新C[xj]并从集合 (*) 中A[i-1]删除。x(j+1), ..., xm
  • 当我们插入时dp[i],我们只是通过弹出前面来删除所有元素dp[j] >= dp[i]
  • 当我们删除 时i-k,(*) 中被破坏的某些元素现在可能变得最好。因此,如果有必要,我们会更新C并插入最后一个元素。

复杂性:(集合中O(n log n)最多可能有插入)。2n

这段代码总结了主要思想:

template<class T> void relaxmax(T& r, T v) { r = max(r, v); }

vector<int> dp(n + 1);
vector<int> C(n + 1, -INF);
vector<int> q(n + 1);
vector<int> ne(n + 1, -INF);
int qback = 0, qfront = 0;
auto cmp = [&](const int& x, const int& y) {
    int vx = dp[x] + C[x], vy = dp[y] + C[y];
    return vx != vy ? vx < vy : x < y;
};
set<int, decltype(cmp)> s(cmp);

dp[0] = 0;
s.insert(0);
q[qfront++] = 0;

for (int i = 1; i <= n; ++i) {
    C[i] = A[i - 1];
    auto it_last = lower_bound(q.begin() + qback, q.begin() + qfront, i, [=](const int& x, const int& y) {
        return C[x] > C[y];
    });

    for (auto it = it_last; it != q.begin() + qfront; ++it) {
        s.erase(*it);
        C[*it] = A[i - 1];
        ne[*it] = i;
        if (it == it_last) s.insert(*it);
    }

    dp[i] = dp[*s.begin()] + C[*s.begin()];

    while (qback < qfront && dp[q[qfront]] >= dp[i]) {
        s.erase(q[qfront]);
        qfront--;
    }

    q[qfront++] = i;
    C[i] = -INF;
    s.insert(i);

    if (q[qback] == i - k) {
        s.erase(i - k);

        if (qback + 1 != qfront && ne[q[qback]] > q[qback + 1]) {
            s.erase(q[qback + 1]);
            relaxmax(C[q[qback + 1]], C[i - k]);
            s.insert(q[qback + 1]);
        }

        qback++;
    }
}

// answer: dp[n]
Run Code Online (Sandbox Code Playgroud)

这次我根据您的算法对其进行了压力测试:请参阅此处

如果仍然不清楚,请告诉我。