首先把題目改為:求 \pm (s_1-s_2) \pm (s_2-s_3) \pm ... \pm (s_{k-1}-s_k) 的最大值,因為可以簡單證明前式達到最大值若且唯若題目要求的式子達到最大值,具體來說是因為如果上式中其中一項是負的話,就顯然不會是最大值了,因此最大值發生在每項都是正的情形,也就和加了絕對值沒兩樣了。而轉換成 maximize 這個函式的話就可以用DP了:記 dp[i][j][c] 代表在a_1,a_2,...,a_i中取 j 段連續的數,假設他們的和分別為s_1,...,s_j,則其值為\pm (s_1-s_2) \pm (s_2-s_3) \pm ... \pm (s_{j-1}-s_j) + c\cdot s_j 的最大值,其中c = 1,-1(實作上是用 0 來代表 c=-1,因此這裡都把下標寫為-1)。但這邊要注意到 j=k 時會不太一樣,他的值應該要是\pm (s_1-s_2) \pm (s_2-s_3) \pm ... \pm (s_{k-1}-s_k)(不管 c 為多少),而這可以在轉移的時候多加留意就好了。
因此再來要看如何轉移,首先dp[i][j][c]當然可以轉移到dp[i-1][j][c],也就是s_j裡面不包含a_i的情形。而如果有包含,那麼就可以轉移到dp[i-r][j-1][-1]+(c+1)\cdot (a_{i-r+1}+...+r_i),或是dp[i-r][j-1][1]+(c-1)\cdot (a_{i-r+1}+...+r_i)。這邊要注意到這是j\neq 1且j\neq k時的轉移式,當j=1時轉移到dp[i-r][j-1][-1]的總和那項的前面係數必須是 c,而轉移到dp[i-r][j-1][1] 的係數也一樣是c。至於j=k時兩個係數則分別為1和-1,這些都可以由前面dp值的定義得出。因此這樣就得到了一個O(n^2k)的算法了,直接DP就可以了,code 為下面的第一份 code。
接下來我們要想辦法把他改進成O(nk)的,而對於這種轉移式通常就是維護某種前綴的max值,使得到時候在算好幾個數的max值的時候可以直接O(1)得知。這裡以dp[i][j][1]為例,當他想轉移到dp[i-r][j-1][-1]時,我們會需要以下所有數的max值:
\begin{cases}dp[i-1][j-1][-1]+2a_i\\dp[i-2][j-1][-1]+2(a_{i-1}+a_i)\\dp[i-3][j-1][-1]+2(a_{i-2}+a_{i-1}+a_i)\\ \;\;\;\;\;\;\;\;\;\;\;\;\; \vdots\end{cases}
因此可以想到直接令一個陣列g[i][j]等於上面那一陀值,那麼就可以得到g的轉移式為;g[i][j]=max(g[i-1][j],dp[i-1][j-1][-1])+2\cdot a_i,因此可以在一算完dp[i][j][-1]的時候就馬上計算g[i+1][j+1]的值,這樣之後要算dp[i+1][j+1][1]時就可以O(1)轉移了。
但上面只是其中一個情形,也就是「要算dp[i][j][1]且他想要轉移到dp[i-r][j-1][-1]時所得到的值」,實際上我們總共會需要8個g陣列,因為還要有「要算dp[i][j][-1]」時所需要的值,還有不要忘了前面的轉移式中有c-1和c+1,也就是上面那陀max中右邊累加和的係數也可能有其他值,因此必須對每種情形都紀錄一個g陣列。不過他們的轉移都一樣,所以可以直接用個 for 把所有的g值一起更新。
code(E1):
#include<bits/stdc++.h> #define INF 1000000000 using namespace std; const int maxn=30000+10 , maxk=200+10 ; int a[maxn] ; int dp[maxn][maxk][2] ; main() { int n,k ; scanf("%d%d",&n,&k) ; for(int i=1;i<=n;i++) scanf("%d",&a[i]) , a[i]+=a[i-1] ; fill(dp[0][0],dp[n][k]+2,-INF) ; dp[0][0][0]=dp[0][0][1]=0 ; for(int i=1;i<=n;i++) for(int j=0;j<=k;j++) for(int c=0;c<2;c++) { int &x=dp[i][j][c] ; if(!j){x=0 ; continue ;} x=max(x,dp[i-1][j][c]) ; int c1=(c==0 ? 0 : 2) , c2=c1-2 ; if(j==1) c1-- , c2++ ; else if(j==k) c1=1 , c2=-1 ; for(int r=1;r<=i;r++) x=max(x,dp[i-r][j-1][0]+c1*(a[i]-a[i-r])) , x=max(x,dp[i-r][j-1][1]+c2*(a[i]-a[i-r])) ; } printf("%d\n",max(dp[n][k][0],dp[n][k][1])) ; }code(E2):
#include<bits/stdc++.h> #define INF 1000000000 using namespace std; const int maxn=30000+10 , maxk=200+10 ; int a[maxn] ; int dp[maxn][maxk][2],g[maxn][maxk][2][5] ; main() { int n,k ; scanf("%d%d",&n,&k) ; for(int i=1;i<=n;i++) scanf("%d",&a[i]) , a[i]+=a[i-1] ; fill(dp[0][0],dp[n][k]+2,-INF) ; fill(g[0][0][0],g[n][k][1]+5,-INF) ; dp[0][0][0]=dp[0][0][1]=0 ; for(int i=0;i<2;i++) for(int j=-2;j<=2;j++) g[1][1][i][j+2]=j*a[1] ; for(int i=1;i<=n;i++) for(int j=0;j<=k;j++) for(int c=0;c<2;c++) { int &x=dp[i][j][c] ; if(!j) x=0 ; else { x=max(x,dp[i-1][j][c]) ; int c1=(c==0 ? 0 : 2) , c2=c1-2 ; if(j==1) c1-- , c2++ ; else if(j==k) c1=1 , c2=-1 ; x=max(x,g[i][j][0][c1+2]) ; x=max(x,g[i][j][1][c2+2]) ; } if(c==0) for(int r=-1;r<=2;r++) g[i+1][j+1][0][r+2]=max(g[i][j+1][0][r+2],x)+r*(a[i+1]-a[i]) ; else for(int r=-2;r<=1;r++) g[i+1][j+1][1][r+2]=max(g[i][j+1][1][r+2],x)+r*(a[i+1]-a[i]) ; } printf("%d\n",max(dp[n][k][0],dp[n][k][1])) ; }
沒有留言:
張貼留言