[英]Optimize: Divide an array into continuous subsequences of length no greater than k such that sum of maximum value of each subsequence is minimum
将O(n^2)
算法优化为O(n log n)
。
问题陈述
给定由n
正整数组成的数组A
将数组分成长度不大于k
连续子序列,使得每个子序列的最大值之和最小。 这是一个例子。
如果n = 8
且k = 5
且数组元素为1 4 1 3 4 7 2 2
,则最佳解为1 | 4 1 3 4 7 | 2 2
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
),
// 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)
时间内解决这个问题,并且有一些人能够通过测试用例。 如何优化?
注意:我将稍微改变您的动态编程关系,以便在j = 0
没有特殊情况。 现在dp[j]
是前j
项A[0], ..., A[j-1]
的答案,并且:
dp[i] = min(dp[j] + max(A[j], ..., A[i-1]), ik <= j < i)
问题的答案现在是dp[n]
。
请注意,如果j < i
和dp[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
是我们在动态编程步骤中的当前索引,即您的 C++ 程序中的i
)。
然后,您可以在内存中保留一些索引x1 < ... < xm
(动态规划关系转换的“有趣”索引),例如: dp[x1] < ... < dp[xm]
(1 )。 然后自动C[x1] >= ... >= C[xm]
(2)。
要存储{x1, ..., xm}
,我们需要一些支持以下操作的数据结构:
i
移动到i+1
,我们必须说ik
现在无法访问)或前端(参见插入)。x
(当我们计算dp[i]
,我们在保留 (1) 的同时插入它,通过删除相应的元素)。min(dp[xj] + C[xj], 1 <= j <= m)
。 因此,一些用于存储x1, ..., xk
队列以及用于存储所有dp[xi] + C[xi]
的set
就足够了。
当我们插入一个元素i
时,我们如何同时保留 (1) 和更新C
?
dp[i]
之前,我们用A[i-1]
更新C
为此,我们找到集合x
st C[xj] <= A[i-1]
最小的元素xj
。 然后 (1) 和 (2) 意味着dp[j'] + C[j'] >= dp[j] + C[j]
对于所有j' >= j
,所以我们将C[xj]
更新为A[i-1]
然后我们从集合 (*) 中删除x(j+1), ..., xm
。dp[i]
,我们只是通过弹出 front 来删除所有元素 st dp[j] >= dp[i]
。ik
,有可能在 (*) 中销毁的某些元素现在变得最好。 因此,如有必要,我们更新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]
这次我根据您的算法对其进行了压力测试:请参阅此处。
如果还不清楚,请告诉我。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.