1. 程式人生 > 其它 >【Tai_mount】演算法學習 - 單調佇列優化 - luoguP5858「SWTR-03」Golden Sword

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