1. 程式人生 > 其它 >CF1077F2.Pictures with Kittens (hard version) 題解 單調佇列優化dp

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;
}