CF1077F2.Pictures with Kittens (hard version) 題解 單調佇列優化dp
阿新 • • 發佈:2021-10-05
題目連結: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)\)
示例程式:
#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; }