最大多段子串和问题是一个杭电ACM的OJ题,是一个很典型的动态规划问题。
网上关于这个问题有很多帖子都给出了相应的问题解决方法并附有代码,但是我一开始看的时候非常费解,闲下来仔细琢磨了一下这个问题,这里将给出一个我对这个问题的理解。

首先我们看看这个问题的描述:
给定一个整数数组A[];
给定数组元素个数N;
给定整数M;

求A[]中任意M个不相交的子串的最大和。

例如,给定A[] = {3, -2, 1, 4 ,-2, 5}, M = 2, N = 6,
那么,它拥有最大和的两(M)个子串为{3},{1, 4, -2, 5},最大和为3 + 8 = 11

这个问题如何解决呢?

一开始就提到过这是个典型的动态规划问题,动态规划问题最核心的步骤就是找状态转移方程。
首先我给出一个比较直观的解决方案,如下:

首先我们定义:
1. maxsum[i][j]为前j个元素中i段和的最大值,也就是目标函数,最后我们要求的也就是max[M][N]
2. end_maxsum[i][j]为前j个元素中以第j个元素结尾的i段和的最大值

那么我们可以得到如下状态转移方程:

end_maxsum[i][j] = MAX(end_maxsum[i][j - 1] + A[j], maxsum[i - 1][j - 1] + A[j]);            ----------------- 1
maxsum[i][j] = MAX(end_maxsum[i][j], maxsum[i][j - 1]);                                      ----------------- 2

首先我们看第一个方程,end_maxsum[i][j]代表以第j个元素结尾的i段和的最大值,那么有两种情况:
Case 1:
将A[j]直接接在第j-1个元素后面,并入前一个段,那么之前的分段必须以A[j-1]结尾,故为end_maxsum[i][j - 1],{A[k],...}{...,A[j-1]} + A[j] -> {A[k],...}{...,A[j - 1], A[j]}, 作这种拼接不会增加段数i,此种情况下最大为end_maxsum[i][j - 1] + A[j];

Case 2:
让A[j]独立作为一段,那么此时最大为maxsum[i - 1][j - 1] + A[j];

将二者取一个最大的即可得到end_maxsum[i][j]。

得到了end_maxsum[i][j]之后,我们如何得到maxsum[i][j]呢?
首先end_maxsum[i][j]肯定是maxsum[i][j]的一个候选,它代表将A[j]拉入该多段子串的最大和;
还有什么情况呢?
那么就是丢弃A[j]的最大和,丢弃A[j]意味着maxsum[i][j] = maxsum[i][j - 1];
所以我们只需要取MAX(end_maxsum[i][j], maxsum[i][j - 1])就可以得到maxsum[i][j]了。

自此,我们已经将动态规划思路理清,现在我们就可以构造一个自底向上的解了(一般来说动态规划都可以理解为自底向上地根据转移方程填表),下面给出一个C++的简单实现。
输入格式为:
M N A[1] A[2] ... A[N]
输出为最大和。


 1 #include <iostream>
 2 
 3 #define MAX 1000
 4 #define MIN -1000
 5 
 6 using namespace std;
 7 
 8 int a[MAX];
 9 int maxsum[MAX][MAX];
10 int end_maxsum[MAX][MAX];
11 
12 
13 int Max(int i, int j)
14 {
15     return i > j ? i : j;
16 }
17 
18 int getTotalSum(int n)
19 {
20     int sum = 0;
21     for(int i = 1; i <= n; i++)
22     {
23         sum += a[i];
24     }
25     return sum;
26 }
27 
28 int getMax(int m, int n)
29 {
30     if(n == m)
31         return getTotalSum(n);
32 
33     for(int i = 1; i <= m; i++)
34     {
35         maxsum[i][i] = getTotalSum (i);
36         end_maxsum[i][i] = getTotalSum (i);
37     }
38 
39     for(int i = 1; i <= m; i++)
40     {
41         int tempMax = getTotalSum (i);
42         for(int j = i + 1; j <= n; j++)
43         {
44             end_maxsum[i][j] = Max(end_maxsum[i][j - 1+ a[j], maxsum[i - 1][j - 1+ a[j]);
45             tempMax = Max(end_maxsum[i][j], maxsum[i][j - 1]);
46             maxsum[i][j] = tempMax;
47 
48             //cout<<"dp["<<i<<"]["<<j<<"] "<<dp[i][j]<<endl;
49         }
50     }
51     return maxsum[m][n];
52 }
53 
54 int main()
55 {
56     int n, m;
57     int result;
58     while(cin>>m>>&& (m > 0 || n > 0))
59     {
60         a[0= 0;
61         for(int i = 0; i < n; i++)
62         {
63             cin>>a[i + 1];
64         }
65         
66         result = getMax(m, n);
67         cout<<result<<endl;
68     }
69     
70     return 0;
71 }

这里需要说明一下,为了让这个实现和之前描述的解题思路一致,这里采用了二维数组来记录maxsum和end_maxsum,记录了很多没有用的信息,空间耗费较大,所以无法通过OJ。
实际上是可以用一维数组来记录这些信息的,这样可以大大减少空间,对应的实现在很多帖子上已经出现,大家可以去参考,思路和上面描述的差不多,但是实现上更加节省空间。
希望能对大家理解这个问题有所帮助。
欢迎各位大牛批评指正。

01/07/2014