CF1077F2.Pictures with Kittens (hard version) 题解 单调队列优化dp
题目链接:https://codeforces.com/problemset/problem/1077/F2
题目大意:
在长度为 \(n\) 的序列里面选择恰好 \(x\) 个元素,使得所有长度 \(\ge k\) 的连续子序列里面都至少包含一个选择的元素。求 \(x\) 个选择的元素的最大和。
解题思路:
动态规划。定义状态 \(f_{i,j}\) 表示表示选择 \(a_i\) 作为第 \(j\) 个数的情况下的最大数字和。
则状态转移方程为 \(f_{i,j} = \max \{ f_{i-k,j-1}, f_{i-k+1,j-1}, \ldots, f_{i-1,j-1} \}\)
但是直接这么写的话时间复杂度是 \(O(n \cdot x \cdot k)\),可以使用单调队列优化,时间复杂度讲到 \(O(n \cdot x)\)。
示例程序:
#include <bits/stdc++.h>
using namespace std;
const int maxn = 5005;
deque<int> que;
int n, k, x, a[maxn];
long long f[maxn][maxn], ans = -1;
int main() {
scanf("%d%d%d", &n, &k, &x);
for (int i = 1; i <= n; i ++)
scanf("%d", a+i);
memset(f, -1, sizeof(f));
for (int i = 1; i <= k; i ++)
f[i][1] = a[i];
for (int j = 2; j <= x; j ++) {
que.clear();
for (int i = j; i <= min(n, j*k); i ++) {
if (f[i-1][j-1] != -1) {
while (!que.empty() && f[que.back()][j-1] <= f[i-1][j-1])
que.pop_back();
que.push_back(i-1);
}
assert(!que.empty());
if (i - que.front() > k) que.pop_front();
assert(!que.empty());
f[i][j] = max(f[i][j], f[que.front()][j-1] + a[i]);
}
}
for (int i = 0; i < k; i ++)
ans = max(ans, f[n-i][x]);
printf("%lld\n", ans);
return 0;
}
滚动数组优化
上面的代码空间复杂度是 \(O(n \cdot x)\),稍微有点大,所以课时使用滚动数组优化,代码如下:
#include <bits/stdc++.h>
using namespace std;
const int maxn = 5005;
deque<int> que;
int n, k, x, a[maxn];
long long f[maxn][2], ans = -1;
int main() {
scanf("%d%d%d", &n, &k, &x);
for (int i = 1; i <= n; i ++)
scanf("%d", a+i);
memset(f, -1, sizeof(f));
for (int i = 1; i <= k; i ++)
f[i][1] = a[i];
for (int j = 2; j <= x; j ++) {
int J = j%2;
que.clear();
for (int i = 0; i <= n; i ++) f[i][J] = -1;
for (int i = j; i <= min(n, j*k); i ++) {
if (f[i-1][J^1] != -1) {
while (!que.empty() && f[que.back()][J^1] <= f[i-1][J^1])
que.pop_back();
que.push_back(i-1);
}
assert(!que.empty());
if (i - que.front() > k) que.pop_front();
assert(!que.empty());
f[i][J] = max(f[i][J], f[que.front()][J^1] + a[i]);
}
}
for (int i = 0; i < k; i ++)
ans = max(ans, f[n-i][x%2]);
printf("%lld\n", ans);
return 0;
}