1. 程式人生 > >$Luogu P2029$ 跳舞 題解

$Luogu P2029$ 跳舞 題解

一道不是十分水的\(dp\).

首先我們考慮\(dp\)方程的構造。起初我定義的狀態是\(dp_{i,j}\)表示前\(i\)個格子,總共跳了\(j\)次的最大得分。但事實上它並不可以轉移,因為我們不知道新的一輪操作從之間的哪個格子算起。

那麼狀態轉移方程就出來了,我們把第一維改成本次跳到第\(i\)個格子上,包括本次在內總共跳了\(j\)次的最大得分,那麼轉移的時候,由於本次一定要跳到\(i\)上(如狀態中所定義),所以不用分類討論。方程就是:\[dp_{i,j}=\max\{dp_{k,j-1}-\rm{Sum(k + 1, i-1)}\}+A_i\]
其中\(0 \leq k < i(\text{不能兩次跳到同一個格子上所以右區間為開區間})\)

\(\rm{Sum(l,r)}\mathcal{=\sum\limits_{i=l}^{r}A_i}\)

程式碼大概是這樣\((\rm{30pts})\)

#include <cstdio>
#include <iostream>

#define MAXN 5010 

using namespace std ; int i, j, k, Ans ;
int N, T, S[MAXN], dp[MAXN][MAXN], A[MAXN], B[MAXN] ;

int main(){
    cin >> N >> T ;
    for (i = 1 ; i <= N ; ++ i) 
        scanf("%d", &A[i]), S[i] = S[i - 1] + A[i] ;
    for (i = 1 ; i <= N ; ++ i) scanf("%d", &B[i]) ;
    for (i = 0 ; i <= N ; ++ i) 
        for (j = 0 ; j <= N ; ++ j)
            dp[i][j] = -192608170 ; dp[0][0] = 0 ;
    for (i = 1 ; i <= N ; ++ i)
        for (j = 1 ; j <= i ; ++ j){
            for (k = 0 ; k < i ; ++ k)
                dp[i][j] = max(dp[i][j], dp[k][j - 1] - S[i - 1] + S[k] + A[i]) ;
            if (j % T == 0) dp[i][j] += B[i] ; Ans = max(Ans, dp[i][j]) ;
        }
    cout << Ans << endl ; return 0 ;
}

但是我們發現,這個複雜度是\(\Theta(n^3)\)的,於是選擇優化。\(dp\)優化的老套路就是:

  • 優化狀態維數

  • 優化轉移複雜度

而此處我們不可以優化狀態了,所以考慮優化轉移複雜度。轉移的複雜度是\(\Theta(n)\)的,我們考慮可否\(\Theta(1)\)轉移,最終使得總複雜度為\(\Theta(n^2) \times \Theta(1) \leq O(n^2)\)

從狀態轉移方程入手,我們發現有關於\(k\)是滿足單調性的。所以不妨我們記錄一下每次的\(k\),即把\(dp[k][j-1]+ S[k]\)中的最大值儲存下來,從而達到\(\Theta(1)\)

轉移的目的。

此處筆者使用了比較玄學的儲存方式……類似刷表……當然這個地方有多種的優化方式啦~

完整版\(code\)(700~800ms):

#include <cstdio>
#include <iostream>

#define MAXN 5010 

using namespace std ; int i, j, k, p, Ans ;
int N, T, Last[MAXN], S[MAXN], dp[MAXN][MAXN], A[MAXN], B[MAXN] ;

int main(){
    cin >> N >> T ;
    for (i = 1 ; i <= N ; ++ i) 
        scanf("%d", &A[i]), S[i] = S[i - 1] + A[i] ;
    for (i = 1 ; i <= N ; ++ i) scanf("%d", &B[i]) ;
    for (i = 0 ; i <= N ; ++ i) 
        for (j = 0 ; j <= N ; ++ j)
            dp[i][j] = -192608170 ; dp[0][0] = 0 ;
    for (j = 1 ; j <= N ; ++ j){
        for (i = j ; i <= N ; ++ i){
            p = Last[i - j], Last[i - j] = 0 ;
            dp[i][j] = p - S[i - 1] + A[i] ; 
            if (j % T == 0) dp[i][j] += B[i] ; Ans = max(Ans, dp[i][j]) ;
            Last[i - j] = max(Last[i - j - 1], dp[i][j] + S[i]) ;
        }
    }
    cout << Ans << endl ; return 0 ;
}

毒瘤常數優化後被艹到龜速的版本(1100ms +):

#include <cstdio>
#include <cstring>
#include <iostream>

#define max Max
#define MAXN 5010 
#define Inf 19260817

using namespace std ; int i, j, k, p, t, Ans ;
int N, T, Last[MAXN], S[MAXN], dp[MAXN][MAXN], A[MAXN], B[MAXN] ;


inline int Max(int a, int b){
    return a & ((b - a) >> 31) | b & ( ~ (b - a) >> 31) ;
}
inline int qr(){
    int res = 0 ; char c = getchar() ;
    while (!isdigit(c)) c = getchar() ;
    while (isdigit(c)) res = (res << 1) + (res << 3) + c - 48, c = getchar() ;
    return res ;
}
int main(){
    cin >> N >> T ; 
    for (i = 0 ; i <= N ; ++ i)
        for (j = 0 ; j <= N ; ++ j)
            dp[i][j] = -Inf ; dp[0][0] = 0 ;
    for (i = 1 ; i <= N ; ++ i) 
        A[i] = qr(), S[i] = S[i - 1] + A[i] ;
    for (i = 1 ; i <= N ; ++ i) B[i] = qr() ; 
    for (j = 1 ; j <= N ; ++ j){
        for (i = j ; i <= N ; ++ i){
            t = i - j, p = Last[t], dp[i][j] = p - S[i - 1] + A[i] ; 
            if (!(j % T)) dp[i][j] += B[i] ; Ans = max(Ans, dp[i][j]), Last[t] = max(Last[t - 1], dp[i][j] + S[i]) ;
        }
    }
    cout << Ans << endl ; return 0 ;
}

唉,先有常數後有天,反向優化\(Sun\)神仙啊