$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{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\)神仙啊