删好串(区间dp)
problem
定義長度為 nnn 的“好”的串 aaa 滿足:
- ∣ai?ai?1∣=1,i∈[2,n]|a_i-a_{i-1}|=1,i\in[2,n]∣ai??ai?1?∣=1,i∈[2,n]。
- ai≥ai?1+ai+12,i∈[2,n?1]a_i\ge \frac{a_{i-1}+a_{i+1}}{2},i\in[2,n-1]ai?≥2ai?1?+ai+1??,i∈[2,n?1]。
給定長度為 nnn 的序列 a,va,va,v,每次選擇一段 aaa 的“好”子串刪除,剩下的子串拼接在一起。
若刪除子串長度為 xxx,則會獲得 vxv_xvx? 的價值。
不一定要刪完,求最大價值。
n≤400,∣vi∣≤1e5,1≤ai≤1e9n\le 400,|v_i|\le 1e5,1\le a_i\le 1e9n≤400,∣vi?∣≤1e5,1≤ai?≤1e9。
solution
observation:子串要么是單增,要么是單減,要么是先單增后單減,即/,\,/\。顯然 \/ 情況下的最低點無法滿足條件 222。
先不考慮“不一定全刪完”,我們就要求必須刪完。
設 fl,r:[l,r]f_{l,r}:[l,r]fl,r?:[l,r] 全部刪完的最大價值。顯然是區間 dpdpdp 轉移。(dpdpdp 轉移就是要歸到子問題上)
-
考慮最后一次刪除 l,rl,rl,r 不是一起被刪的,那么肯定存在一個中間點 ppp 分割這段區間。
即 fl,r←fl,p+fp+1,rf_{l,r}\leftarrow f_{l,p}+f_{p+1,r}fl,r?←fl,p?+fp+1,r?。
-
考慮最后一次刪除 l,rl,rl,r 是一起被刪的。
那么我們就需要知道最后一次的單增/單減序列的價值。
設 gl,r:[l,r]g_{l,r}:[l,r]gl,r?:[l,r] 刪到只剩一個單增序列的最大價值。
設 hl,r:[l,r]h_{l,r}:[l,r]hl,r?:[l,r] 刪到只剩一個單減序列的最大價值。
我們直接枚舉兩個序列的交點,即最高點 iii。
fl,r←gl,i+hi,r+v(ai?2?al?ar+1)f_{l,r}\leftarrow g_{l,i}+h_{i,r}+v(a_i*2-a_l-a_r+1)fl,r?←gl,i?+hi,r?+v(ai??2?al??ar?+1)。
當然還有只有單增/單減情況,但要求 al,ara_l,a_ral?,ar? 滿足相應大小關系。
fl,r←gl,r+v(ar?al+1)al≤arf_{l,r}\leftarrow g_{l,r}+v(a_r-a_l+1)\quad a_l\le a_rfl,r?←gl,r?+v(ar??al?+1)al?≤ar?。
fl,r←hl,r+v(al?ar+1)al≥arf_{l,r}\leftarrow h_{l,r}+v(a_l-a_r+1)\quad a_l\ge a_rfl,r?←hl,r?+v(al??ar?+1)al?≥ar?。
至于 g,hg,hg,h 的轉移,同樣枚舉接在 rrr 前的元素 iii,即可變成子問題。
gl,r←gl,i+fi+1,r?1ai+1=arg_{l,r}\leftarrow g_{l,i}+f_{i+1,r-1}\quad a_i+1=a_rgl,r?←gl,i?+fi+1,r?1?ai?+1=ar?。
hl,r←hl,i+fi+1,r?1ai?1=arh_{l,r}\leftarrow h_{l,i}+f_{i+1,r-1}\quad a_i-1=a_rhl,r?←hl,i?+fi+1,r?1?ai??1=ar?。
最后再來個 dpi:[1,i]dp_i:[1,i]dpi?:[1,i] 一定刪掉 [k,i][k,i][k,i] 連續一段,但前面不要求刪完的最大價值。
枚舉 jjj,dpj+fk,i→dpidp_j+f_{k,i}\rightarrow dp_idpj?+fk,i?→dpi?,前綴最大值優化,轉移,dpndp_ndpn? 就是最后的結果了。
時間復雜度 O(n3)O(n^3)O(n3)。
code
#include <bits/stdc++.h> using namespace std; #define maxn 405 #define int long long #define inf 0x3f3f3f3f int n; int v[maxn], a[maxn], dp[maxn]; int f[maxn][maxn], g[maxn][maxn], h[maxn][maxn];signed main() {freopen( "good.in", "r", stdin );freopen( "good.out", "w", stdout );scanf( "%lld", &n );for( int i = 1;i <= n;i ++ ) scanf( "%lld", &v[i] );for( int i = 1;i <= n;i ++ ) scanf( "%lld", &a[i] );//f[l][r]:刪完[l,r]的最大價值for( int l = n;l;l -- )for( int r = l;r <= n;r ++ ) {f[l][r] = g[l][r] = h[l][r] = -inf;g[l][l] = h[l][l] = 0;for( int i = l;i <= r;i ++ ) {f[l][r] = max( f[l][r], f[l][i] + f[i + 1][r] );//case1 枚舉斷點if( a[i] - 1 == a[r] ) h[l][r] = max( h[l][r], h[l][i] + f[i + 1][r - 1] );//h[l][r]:將[l,r]刪到只剩一個下坡的最大價值 且l,r一定不被刪if( a[i] + 1 == a[r] ) g[l][r] = max( g[l][r], g[l][i] + f[i + 1][r - 1] );//g[l][r]:將[l,r]刪到只剩一個上坡的最大價值 且l,r一定不被刪if( a[l] <= a[i] and a[i] >= a[r] ) f[l][r] = max( f[l][r], g[l][i] + h[i][r] + v[(a[i] << 1) - a[l] - a[r] + 1] );//case2 枚舉最高點/連接點}if( a[l] <= a[r] ) f[l][r] = max( f[l][r], g[l][r] + v[a[r] - a[l] + 1] );//case3直接一個遞增序列無凸點if( a[l] >= a[r] ) f[l][r] = max( f[l][r], h[l][r] + v[a[l] - a[r] + 1] );//case4直接一個遞減序列無凸點}for( int i = 1;i <= n;i ++ ) {dp[i] = max( dp[i], dp[i - 1] );for( int j = i;j <= n;j ++ ) dp[j] = max( dp[j], dp[i - 1] + f[i][j] );//枚舉刪去[l,r]一段并加上前面剩下的最大值(利用前綴最大值優化dp)}printf( "%lld\n", dp[n] );return 0; }總結
- 上一篇: 电脑桌面底下一排图标没了怎么办电脑桌面下
- 下一篇: 图中异色点对最短距离(最小生成树+线段树