單調佇列最佳化DP
簡單好想的DP最佳化
真正的教育是把學過的知識忘掉後剩下的東西 —— ***
對於一個轉移方程類似於 \(dp[i]=max(min)\{dp[j]+b[j]+a[i]\}\ \ x_i<=j<=y_i\) 的DP,如果暴力實現的話複雜度是 \(O(n^2)\),實現方法是雙層for迴圈巢狀。但如果區間 \([x_i,y_i]\) 與區間 \([x_{i+1},y_{i+1}]\) 存在交集,那麼我們在使用 \(j\) 進行遍歷時就會產生重複計算,而單調佇列最佳化DP就是解決這一重複計算的法寶。
例題
[USACO11OPEN] Mowing the Lawn G
很好的單調佇列DP入門題。設 dp[i]
表示選擇第 \(i\) 項元素的合法序列的最大和。那麼可得轉移方程 \(dp[i]=max\{dp[j-1]-sum[j]\}+sum[i]\) 。其中 \(j\in[i-m,i-1]\),這裡的 \(j\) 可以理解為兩段連續區間的斷開處,並且 \(j\) 是可以等於 \(0\) 的。但是如果DP包含了 \(0\),那必然會涉及 \(dp[-1]\) 的計算。不妨在整個序列前加入一個 \(0\),在進行DP,那麼就可以解決這一問題。
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N = 2e5 + 5;
int n, k, dp[N], sum[N], p[N], tail=1, head=0, ans = INT_MIN;
signed main(){
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
cin>>n>>k;
for(int i=2, a; i<=n+1; ++i) cin>>a, sum[i] = sum[i-1] + a;
for(int i=1; i<=n+1; ++i){
while(tail > head && dp[p[tail]-1]-sum[p[tail]] <= dp[i-1]-sum[i]) --tail;
p[++tail] = i;
while(p[head] < i-k) ++head;
dp[i] = dp[p[head]-1] - sum[p[head]] + sum[i];
ans = max(ans, dp[i]);
} return cout<<ans, 0;
}