【Tai_mount】演算法學習 - 單調佇列優化 - luoguP5858「SWTR-03」Golden Sword
單調佇列
https://www.cnblogs.com/ljy-endl/p/11638389.html
本次是看這個教程學習的
什麼時候用單調佇列?
在一個數列中,求多個區間的最值。比如求數列a[]中每個數之前m個數中的最小值。
正常來說這是n*m的複雜度,但單調佇列就可以將其優化為n的複雜度
演算法核心淺析(以求區間最小值為例)
給定數列a[n],求每個數前m個數的最小值。
準備一個佇列,隊首和隊尾都能出隊,僅隊尾可以入隊。佇列中儲存a[]中下標。
遍歷i=1~n
每次首先列印隊頭,然後根據條件彈出:(彈出均用while)
隊頭彈出條件:q[head]<i-m+1 因為往後這個隊頭都不會再用到了,它永遠是m個以前
隊尾彈出條件:a[q[tail]]>a[i] 這意味著i優於q[tail],有個隱藏條件i>q[tail],即i一定比q[tail]更晚從隊頭彈出,同時i這個答案又優於q[tail],理所應當q[tail]要滾蛋
結尾把i入隊
(以上建議直接看上面附的連結教程)
事實上這個佇列符合一下條件:單調。q[]是單調的,a[q[]]也同樣是單調的。前者因著我們入隊順序而單調,後者因著我們隊尾彈出條件而單調。而隊頭的彈出保證我們要求的最值是在“前m”這個範圍中。
這個演算法的本質其實是讓我們求的最值能最大化利用起來。
要記住的一點是,單調佇列是那個佇列單調,而要求不是a[]這個原本的數列單調。
優化DP
從P5858這道題我們能學到的是:
1 為什麼j從後往前推
答:因為dp[i][j]要從dp[i-1][j-1~min(w,j-1+s)]中找最值,整個數列是dp[i-1][]
我們把dp[][]畫出來
上面是dp[i-1][],下面是dp[i][]箭頭意味著dp[i-1][]中的某個區間裡產生dp[i][]中某個位置的答案
這個題目和之前簡單的求區間最小值有什麼區別?求區間最小值是求哪個位置的答案,就在之後把那個位置入隊——因為它是求前m個。
而這裡呢,是求前1個,它自己,和後面若干個。肯定在求的時候這些位置都要已經入隊了才好。
這樣你想想,如果從前面推的話,你得提前入隊好幾個。不如從後面推,只需要提前入隊一個就好。
重點是:求之前要保證答案所在的區間全部入隊過
程式碼:
#include<iostream>
#include<cstring>
using namespace std;
const long long N=5007;
long long MAXN=1008600110086001;
long long n,s,w;
long long a[N],monoQ[N];
long long dp[N][N];
void input(){
cin>>n>>w>>s;
for(long long i=1;i<=n;i++){
cin>>a[i];
}
}
void fun(){
for(long long i=0;i<=n;i++) for(long long j=0;j<=w;j++) dp[i][j]=-MAXN;
dp[0][0]=0;
for(long long i=1;i<=n;i++){
long long l=1,r=1;
monoQ[l]=w;
for(long long j=w;j;j--){
while(l<=r&&monoQ[l]>j-1+s){
l++;
}
while(l<=r&&dp[i-1][monoQ[r]]<dp[i-1][j-1]){
r--;
}
monoQ[++r]=j-1;
dp[i][j]=dp[i-1][monoQ[l]]+a[i]*j;
}
}
}
void output(){
long long ans=-MAXN;
for(long long j=1;j<=w;j++){
ans=max(ans,dp[n][j]);
}
cout<<ans;
}
int main(){
input();
fun();
output();
return 0;
}