动态规划:最大m子段和
【问题描述】
给定由 n个整数(可能为负整数)组成的序列,以及一个正整数 m,要求确定序列 m个不相交子段,使得这m个子段的总和达到最大,求出最大和。
【算法思想】
动态规划基本思路: 首先,定义数组seq[n]存储n个整数组成的序列, dp(i,j) 表示由前 j项得到的含i个字段的最大值,且最后一个字段以 seq[j] 结尾!仔细想想,因为必须是以 seq[j] 结尾的,所以seq[j] 或自己独立成一个子段,或与前边以seq[j-1]结尾的子段联合,我们可知:
dp(i,j) = max{ dp(i, j-1)+seq[j], dp(i-1,t)+seq[j] } i-1<= t <=j-1.
其中关键要注意 i-1<= t <=j-1,所求的最后结果为 max( dp[m][n] ) 其中1<=m<=n。然后由此递推式可以构造DP矩阵了,构造出来后你会发现只需要在一个平行四边形里由左到右,在由上到下就可以计算了。
【算法改进】
但是,我们会发现,当n非常大时,这个算法的时间复杂度和空间复杂度是非常高的,时间复杂度近似为O(m*n^2), 空间复杂度近似为O(m*n)。因此,我们需要优化算法来降低时间复杂度和空间复杂度。构造DP矩阵后,可以发现,计算当前行每次只需要知道上一行,所以设计两个数组,一个存储当前行的值now_value,一个存储上一行到该下标为止的最大值pre_max。这样既节省了存储空间,又省去了每次都需要计算上一行的最大值。
【代码】
1 #include <iostream> 2 #include <stdio.h> 3 using namespace std; 4 5 int main() 6 { 7 //read input 8 int m, n, tmp, max_sum; 9 scanf("%d %d", &m, &n); 10 int *seq = new int[n+1]; 11 int **mat = new int*[m+1]; 12 seq[0]=0; 13 for(int i=1; i<n+1; i++){ 14 scanf("%d", &(seq[i])); 15 } 16 for(int i=0; i<m+1; i++){ 17 mat[i] = new int[n+1]; 18 for(int j=0; j<n+1; j++){ 19 mat[i][j] = 0; 20 } 21 } 22 23 //dp 24 for(int i=1; i<=m; i++){ 25 for(int j=i; j<=n-m+i; j++){ //只需要在一个平行四边形内计算即可 26 if(i==j){ 27 mat[i][j] = mat[i-1][j-1]+seq[j]; 28 }else{ 29 max_sum = mat[i][j-1] + seq[j]; 30 for(int k=i-1; k<=j-1; k++){ 31 tmp = mat[i-1][k]+ seq[j]; 32 if( max_sum < tmp ){ 33 max_sum = tmp; 34 } 35 } 36 mat[i][j] = max_sum; 37 } 38 } 39 } 40 41 //output m max_sum 42 max_sum = mat[m][m]; 43 for(int j=m+1; j<=n; j++){ 44 if(max_sum < mat[m][j]){ 45 max_sum = mat[m][j]; 46 } 47 } 48 printf("%d \n", max_sum); 49 50 //post-process 51 delete[] seq; 52 for(int i=0; i<m+1; i++) delete[] mat[i]; 53 delete[] mat; 54 return 0; 55 }
【改进代码】
#include <iostream> #include <stdio.h> using namespace std; int main() { //read input int m, n, tmp, max_sum, row_max; scanf("%d %d", &m, &n); int *seq = new int[n]; int *pre_max = new int[n-m+1]; int *now_value = new int[n-m+1]; for(int i=0; i<n; i++){ scanf("%d", &(seq[i])); } for(int i=0; i<n-m+1; i++){ pre_max[i]=0; now_value[i]=0; } //dp for(int i=0; i<m; i++){ now_value[0] = pre_max[0] + seq[i]; row_max = now_value[0]; pre_max[0] = row_max; for(int j=1; j<n-m+1; j++){ now_value[j] = pre_max[j] > now_value[j-1] ? pre_max[j]+seq[i+j] : now_value[j-1]+seq[i+j]; if(row_max < now_value[j]){ row_max = now_value[j]; } pre_max[j] = row_max; } } //output m max_sum max_sum = now_value[0]; for(int j=1; j<n-m+1; j++){ if(max_sum < now_value[j]){ max_sum = now_value[j]; } } printf("%d \n", max_sum); //post-process delete[] seq; delete[] pre_max; delete[] now_value; return 0; }