題目來源:http://acm.hdu.edu.cn/showproblem.php?pid=1024
(http://www.fjutacm.com/Problem.jsp?pid=1375)
題意:長度爲n的序列裏,m段不相關區間的最大和
思路:我們先要確定一個東西,就是狀態,這裏我用dp[i][j]表示前j個數在取a[j]情況下分i段的最大和;
那麼我們爲了找規律,可以先來一發Excel,就以樣例爲例子:
然後我們可以發現其實紅圈裏的8是狀態dp[2][6](i=2, j=6),那麼我們可以想想這個位置怎麼推導,很明顯,他可以選擇和分i-1塊的最大值相加,得到的i塊可能是最大,或者他也可以直接和同樣分i塊的j-1的位置相加,這樣就相當於不斷開,得到最大。那麼也就是他只有兩種選擇,第一個是dp[i][j-1],第二個是max(dp[i-1][i-1]~dp[i-1][j-1]),也就是dp[i][i~n]只和dp[i-1][i-1~n]這一行的狀態有關,和別的無關。那麼我們就可以用滾動數組保存;但是如果你找max(dp[i-1][i-1]~dp[i-1][j-1])的時候用的是for查找的話,那就涼涼了,因爲那樣複雜度就是O(n^3),也就是我們要用一個maxn來記住之前的最大值,然後每次更新記錄;具體看代碼。
#include<stdio.h>
#include<string.h>
#include<algorithm>
using namespace std;
const int N= 1001000;
const long long INF=0x3f3f3f3f3f3f3f3f;
long long dp[2][N], maxn;
int a[N];
int main( ){
int m, n, t;
while(~scanf("%d%d", &m, &n)){
t=1;///用來滾動數組
for(int i=1; i<=n; ++i)
dp[0][i]=-INF;
for(int i=1; i<=n; ++i)
scanf("%d", &a[i]);
for(int i=1; i<=m; ++i, t=1-t){///t=1-t就是在循環滾動
dp[t][i]=dp[1-t][i-1]+a[i];///對角線的值其實就是前n項和啦!!
maxn=dp[1-t][i-1];///別把這個忘了
for(int j=i+1; j<=n; ++j){
maxn=max(maxn, dp[1-t][j-1]);///maxn更新記錄max(dp[i-1][i-1]~dp[i-1][j-1])
dp[t][j]=max(dp[t][j-1], maxn)+a[j];///狀態的轉移步驟
}
}
t=1-t;///最後i>m時的那一個++i, t=1-t的影響要轉過來
maxn=-INF;
/**
注意,dp[i][j]是表示前j個數在取a[j]情況下分i段的最大和;
也就是dp[m%2][n不一定是最優解,因爲可能不加a[n]還更大;
**/
for(int i=m; i<=n; ++i)
maxn=max(maxn, dp[t][i]);
printf("%I64d\n", maxn);
}
return 0;
}