博主學習本題的經過嚶嚶嚶:
7.22 : 聽學長講(一知半解)——自己推(推不出來)——網上看題解——以為自己會了(網上題解是錯的)——發現錯誤以后又自己推(沒推出來)——給學長發郵件——得到正確解法——按着學長思路又推一遍——最后理解
(前后的“學長”不是同一個人)
7.23 : 寫出代碼,完善細節。
(建議改成:西 天 取 經)
首先,網上對於這道題的題解絕大部分是錯誤的!(比如洛谷上的部分題解)
用LIS做是不行的
玄學貪心是不行的
dp轉移方程不能自圓其說是不行的
即使是AC代碼也不一定是正確的(2009年的省選,數據太太太太太太太太太太太太太太水了嚶嚶嚶)
廢話說完了
~~~~~~~~~~~~~~~嚶嚶嚶來自蒟蒻OIerOrzer的分割線啦嚶嚶嚶~~~~~~~~~~~~~~~~
以下為正文部分嚶嚶嚶:
考慮把一個數列分成兩個集合,有a[i]的為一個集合,沒有a[i]的為一個集合~
我們定義狀態轉移方程dp[i][j]表示對於前i個數,有a[i]的集合的長度為j,沒有a[i]的集合的最后一個數的最小值為dp[i][j](神仙定義)
也就是說,現在有兩個集合,其中一個有a[i],另一個沒有a[i]。嘗試把a[i+1]放到其中一個集合中。
1.嘗試把a[i+1]放到有a[i]的集合當中。那么需要滿足的先決條件就是:a[i+1]>a[i].
此時更新dp[i+1][j+1](因為把a[i]放到長度為j的集合中,於是長度++)此時沒有a[i+1]的集合同時也是沒有a[i]的集合,換句話來說,這個轉移對沒有a[i]的集合是沒有改變的,所以,dp[i+1][j+1]可以直接由dp[i][j]繼承過來。
2.嘗試把a[i+1]放到沒有a[i]的集合當中。那么需要滿足的先決條件就是:a[i+1]>dp[i][j].
此時更新dp[i+1][i-j+1](原來沒有a[i]的集合的長度為(i-j),把a[i+1]放進去,長度++)既然把a[i+1]放到了沒有a[i]的集合中,那么,沒有a[i+1]的集合的最后一個數就是a[i],於是,用a[i]去更新dp[i+1][i-j+1];
(真繞啊嚶嚶嚶)
上代碼嚶嚶嚶:
1 #include <bits/stdc++.h> 2 using namespace std; 3 const int maxn=2000+10; 4 int dp[maxn][maxn],a[maxn]; 5 int n,m; 6 void Solve(){ 7 scanf("%d",&m); 8 while(m--){ 9 scanf("%d",&n); 10 memset(dp,0x3f,sizeof(dp)); 11 memset(a,0x3f,sizeof(a)); 12 for(int i=1;i<=n;++i) scanf("%d",&a[i]); 13 dp[1][1]=-1;//因為數據中可能有0,因此不能初始化為0; 14 for(int i=1;i<=n;++i){ 15 for(int j=1;j<=i;++j){ 16 if(a[i+1]>a[i]) dp[i+1][j+1]=min(dp[i+1][j+1],dp[i][j]); 17 if(a[i+1]>dp[i][j]) dp[i+1][i-j+1]=min(dp[i+1][i-j+1],a[i]); 18 } 19 } 20 if(dp[n][n/2]>1e8) printf("No!\n"); 21 //沒有更新,說明不能將原序列合法地平分成兩部分,就輸出No; 22 else printf("Yes!\n"); 23 } 24 } 25 int main(){ 26 Solve(); 27 return 0; 28 }
完結撒花嚶嚶嚶~(然而並沒有)
上輩子的題了(大霧)(所以這就是火星水嗎)
但是在解決上一道題后會發現對於這道題會有不一樣的理解~~~
我們先定義dp[i][j]表示小烈1走到i,小烈2走到j時的最大收益。且默認小烈1始終在小烈2前面,且前j個已經被送完。
換句話來說,其實根本沒有小烈1和小烈2(只是我們yy出來的)或者說,並不是划分了“小烈1”和“小烈2”這兩個抽象的概念,划分的應該是“送了a[i]的小烈”和“沒有送a[i]的小烈”!
(woc這不就是上一道題嗎,太像了好叭)
現在我們換一種表達方式來定義dp[i][j]:
dp[i][j]表示前i個客人,沒有送過a[i]的小烈最后一個送的是a[j]時的最大收益。
默認j小於i,且前j個一定已經送過了(如果前j個有沒有送過的,那就不合法了,因為兩個小烈不能回頭)
現在考慮a[i+1]由誰送。
1.由送了a[i]的小烈送。所以,沒有送a[i]的小烈同樣沒有送a[i+1]。也就是說,這個轉移完成后,沒有送a[i]的小烈原來在j,現在還是在j,沒有變化。
所以用(dp[i][j]+a[i]*a[i+1])更新(dp[i+1][j])。
2.由沒有送a[i]的小烈送。所以,送了a[i]的小烈沒有送a[i+1]。也就是說,沒有送a[i+1]的小烈最后一個送的是a[i]!
所以用(dp[i][j]+a[j]*a[i+1])更新(dp[i+1][i]).
(和剛才那個一樣繞嚶嚶嚶)
上代碼:

1 #include <bits/stdc++.h> 2 using namespace std; 3 const int maxn=2500+10; 4 int dp[maxn][maxn],a[maxn],ans; 5 void Solve(){ 6 int n;scanf("%d",&n); 7 for(int i=1;i<=n;++i) scanf("%d",&a[i]); 8 for(int i=1;i<=n;++i){ 9 for(int j=0;j<i;++j){//j的范圍要搞清楚哦; 10 dp[i+1][j]=max(dp[i+1][j],dp[i][j]+a[i]*a[i+1]); 11 dp[i+1][i]=max(dp[i+1][i],dp[i][j]+a[j]*a[i+1]); 12 } 13 } 14 for(int i=0;i<n;++i) ans=max(ans,dp[n][i]+a[i]*a[n]); 15 /*現實中的小烈是從1走到n,又從n回去,在dp方程里面我們把一個小烈拆成了兩個, 16 其中一個表示現實中正向走的部分,另一個表示現實中反向走的部分(但是令這一個 17 反向走的小烈反過來走,就是正着走)也就是說,當現實中小烈走到n,開始返回的 18 時候,a[n]與dp方程中定義的小烈2經過的最后一個a[i](也就是反着走的第一個 19 a[i])是要產生一個值的,而這個值要加到答案里面才能得到最終結果*/ 20 printf("%d",ans); 21 } 22 int main(){ 23 Solve(); 24 return 0; 25 }
完結撒花花!!!