1. 程式人生 > >hdu 1024 Max Sum Plus Plus

hdu 1024 Max Sum Plus Plus

題意

n個數取m個不重合的部分使和最大,求這個最大值。

思路

列舉比大小的思路肯定不可取,嘗試動態規劃。
動態規劃最自然的想法是:求n個數取若干部分和的最大值,子問題就是對n-1個數取若干部分和的最大值。
設a[n]為n個數的序列,dpp[n][m]為n個數取m部分的最大值,可以有兩種決策達到dpp[n][m]的狀態:

  1. 第n個數構成第m部分
  2. 第n個數不構成第m部分

所以狀態轉移方程,1xnm+11\leq x\leq n-m+1
dpp[n][m]=max(max(dpp[nx][m1]+i=nx+1na[i]),dpp[n1][m])dpp[n][m]=max(max(dpp[n-x][m-1]+\sum_{i=n-x+1}^n a[i]), dpp[n-1][m])


對於這個方程我們考慮實現,n需要一層遍歷,m需要一層遍歷,x需要一層遍歷,變成的O(n3)O(n^3)的演算法,應該會超時。
嘗試優化第一個決策引入的x,嘗試消除x的影響。
設dp[n][m]為n個數取m部分的最大值,且第n個數構成第m部分,之所以這樣設是因為我觀察到max(dpp[nx][m1]+i=nx+1na[i])max(dpp[n-x][m-1]+\sum_{i=n-x+1}^n a[i])中有這樣(dp[n][m])的部分。(有點類似最長上升子序列的狀態)
dp[n][m]=max(dpp[nx][m1]+i=nx+1na[i])dp[n][m]=max(dpp[n-x][m-1]+\sum_{i=n-x+1}^n a[i])
=max(dpp[nx][m1]+i=nx+1n1a[i])+a[n]=max(dpp[n-x][m-1]+\sum_{i=n-x+1}^{n-1} a[i])+a[n]
=max(dpp[n1][m1],dp[n1][m])+a[n]=max(dpp[n-1][m-1], dp[n-1][m])+a[n]
這樣消除了x的影響,變成了O(n2)O(n^2)的演算法。回過頭在看最後公式的兩部分可以看成:

  • 第n個數獨立構成第m部分的情況
  • 第n個數和前面的數一同構成第m部分的情況

同時dpp[n][m]=max(dp[n][m],dpp[n1][m])dpp[n][m]=max(dp[n][m], dpp[n-1][m])
由於狀態轉移方程只涉及相鄰項的關係,所以可以狀態壓縮。(注意順序)
這題動態規劃的邊界條件是試出來的,詳情看程式碼。
看到航電discuss的另一中寫法,把分成m部分看成階段。

程式碼

#include <stdio.h>
#include <string.h>
#include <algorithm>
using namespace std;

typedef long long LL;
const LL INF = -1e18;
int s[1000010];
LL dp[1000010];
LL dpp[1000010];

int main(){
	int m, n;
	while(~scanf("%d%d", &m, &n)){
		dpp[0] = dp[0] = 0;
		for(int i = 1; i <= m; ++i){
			dp[i] = INF;
			dpp[i] = INF;
		}
		for(int i = 1; i <= n; ++i){
			scanf("%d", s + i);
			for(int j = min(i, m); j > 0; --j){
				dp[j] = max(dpp[j-1], dp[j]) + s[i];
				dpp[j] = max(dpp[j], dp[j]);
			}
		}
		printf("%I64d\n", dpp[m]);
	}
	return 0;
}

總結

  1. 第一次感覺數學是一個工具,並且是一個好工具。