洛谷題單指南-動態規劃2-P1725 琪露諾

江城伍月發表於2024-04-24

原題連結:https://www.luogu.com.cn/problem/P1725

題意解讀:走過一系列格子之後,冰凍指數之和最大,相當於計算最大子序列的和。

解題思路:

設a[0~n]儲存所有冰凍指數

dp[i]表示以第i號格子為終點所能獲得的最大冰凍指數

設j表示i的前一個格子,也就是從j可以移動到i

已知i,則j的範圍也很好計算,由於每次在x移動的範圍是 x + l ~ x + r之間,所以上一步j的範圍是i-r ~ i-l

然後計算dp[j] + a[i]的最大值即可

所以有dp[i] = max(dp[i], dp[j] + a[i]),j取i-r ~ i-l之間,注意j不能小於0

由於冰凍指數有負數,在加法過程中會出現負值,最後要計算最大值,dp的初始化需要小心

看資料範圍:-2×10^5

80分程式碼:

#include <bits/stdc++.h>
using namespace std;

const int N = 2e5 + 5;
int a[N]; //冰凍指數
int dp[N]; //dp[i]表示以第i號格子為終點所能獲得的最大冰凍指數
int n, l, r;

int main()
{
    cin >> n >> l >> r;
    for(int i = 0; i <= n; i++) 
    {
        cin >> a[i];
        dp[i] = -1e9; //由於有負數,dp初始化為-1e9,注意不能初始化為INT_MIN,因為再加負數會溢位
    }
    dp[0] = 0; //0號格子可獲得0冰凍指數
    for(int i = 1; i <= n; i++) //第一個可到達的格子是l
    {
        for(int j = i - r; j <= i - l; j++) //i的上一個格子範圍在i-r~i-l之間
        {
            if(j < 0) continue;
            dp[i] = max(dp[i], dp[j] + a[i]);
        }
    }
    int ans = INT_MIN;
    for(int i = 0; i <= n; i++) 
    {
        if(i + r > n) //下一步的位置編號大於n才算到達對岸
            ans = max(ans, dp[i]);
    }
    cout << ans;

    return 0;
}

以上程式碼的複雜度為O(n^2),最壞情況下是會超時的,必須做出最佳化。

這裡的最佳化點在於,對於每一個i,要在j = i-r ~ i-l範圍內計算dp[j] + a[i]的最大值,也就是找一個範圍是i-r ~ i-l視窗內的最大值。

於是,想到了單調佇列,只需要在i>=l之後(l之前0之後的位置都無法走到),開始維護視窗大小是r - l + 1的單調遞減隊裡即可快速獲取i-r ~ i-l視窗內的最大值。

100分程式碼:

#include <bits/stdc++.h>
using namespace std;

const int N = 2e5 + 5;
int a[N]; //冰凍指數
int dp[N]; //dp[i]表示以第i號格子為終點所能獲得的最大冰凍指數
int q[N]; //單調佇列,存dp的下標
int n, l, r;

int main()
{
    cin >> n >> l >> r;
    for(int i = 0; i <= n; i++) 
    {
        cin >> a[i];
        dp[i] = -1e9; //由於有負數,dp初始化為-1e9,注意不能初始化為INT_MIN,因為再加負數會溢位
    }
    dp[0] = 0; //0號格子可獲得0冰凍指數
    q[0] = 0; //佇列加入一個元素,是dp的下標0
    int head = 0, tail = 0; //隊首、隊尾
    for(int i = l; i <= n; i++) //i第一個可走的位置是l,右邊界正好是0
    {
        dp[i] = dp[q[head]] + a[i]; //直接取視窗內的最大值,即隊首元素對應的值
        //下一個要放入佇列的下標是右邊界加1:i - l + 1
        if(head <= tail && i - l + 1 - q[head] > r - l) head++; //如果佇列元素將超過視窗大小,隊首出隊
        while(head <= tail && dp[q[tail]] <= dp[i - l + 1] ) tail--; //如果隊尾元素對應的dp值不比dp[i-r+1]大,則出隊,確保單調遞減
        q[++tail] = i - l + 1;
    }
    int ans = INT_MIN;
    for(int i = 0; i <= n; i++) 
    {
        if(i + r > n) //下一步的位置編號大於n才算到達對岸
            ans = max(ans, dp[i]);
    }
    cout << ans;

    return 0;
}

相關文章