动态规划:最大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;
}

 

posted @ 2017-08-29 10:16  hedgehog小子  阅读(1991)  评论(0编辑  收藏  举报