原題連結: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;
}