描述
给定一个整数序列S1 ,S2 ,·,Sn (1 ≤ n ≤ 1,000,000,−32768 ≤ Si ≤32768),定义函数
sum(i,j) = Si + ...+ Sj (1 ≤ i ≤ j ≤ n)。
现给定一个正整数 m,找出 m 对 i 和j,使得 sum(i1 ,j1 ) + sum(i2 ,j2 ) + ... + sum(im ,jm ) 最大。这就是最大 M 子段和(maximum m segments sum)。
输入每个测试用例由两个正整数 m 和 n开头,接着是 n 个整数。
输出
每行输出一个最大和。
样例输入
1 3 1 2 3
2 6 -1 4 -2 3 -2 3
样例输出
6
8
分析
设状态为 d[i,j],表示前 j 项分为i 段的最大和,且第 i 段必须包含 S[j],则状态转移方程如下:
d[i,j] = max{d[i,j −1] + S[j],max{d[i − 1,t] + S[j]}}, 其中i ≤ j ≤ n,i − 1 ≤ t < j
target = max{d[m,j]},其中m ≤ j ≤ n
分为两种情况:
• 情况一,S[j] 包含在第 i段之中,d[i,j − 1] + S[j]。
• 情况二,S[j]独立划分成为一段,max{d[i − 1,t] + S[j]}。
观察上述两种情况可知 d[i,j]的值只和 d[i,j-1] 和 d[i-1,t] 这两个值相关,因此不需要二维数组,
可以用滚动数组,只需要两个一维数组,用d[j] 表示现阶段的最大值,即 d[i,j − 1] + S[j],用prev[j] 表示上一阶段的最大值,即max{d[i − 1,t] + S[j]}。
代码
mmss.c
#include<stdio.h>
#include<stdlib.h>
#include<limits.h>
/**
* @brief 最大 m 段子序列和
* @param[in] S 数组
* @param[in] n 数组长度
* @param[in] m m 段
* @return 最大 m 段子序列和
*/
int mmss(int S[], intn, int m) {
int max_sum, i, j;
/* d[i]表示现阶段最大值,prev[i] 表示上阶段最大值 */
/* d[0], prev[0] 未使用*/
int *d = (int*)calloc(n + 1, sizeof(int));
int *prev = (int*)calloc(n + 1, sizeof(int));
S--; // 因为 j 是从 1开始,而 S 从 0 开始,这里要减一
for (i = 1; i <=m; ++i) {
max_sum = INT_MIN;
for (j = i; j <=n; ++j) {
// 状态转移方程
if (d[j - 1] <prev[j - 1])
d[j] = prev[j - 1] +S[j];
else
d[j] = d[j - 1] +S[j];
prev[j - 1] =max_sum; // 存放上阶段最大值
if (max_sum <d[j])
max_sum = d[j]; // 更新max_sum
}
prev[j - 1] =max_sum;
}
free(d);
free(prev);
return max_sum;
}
int main() {
int n, m, i, *S;
while(scanf("%d%d", &m, &n) == 2) {
S = (int*)malloc(sizeof(int) * n);
for (i = 0; i < n;++i)
scanf("%d",&S[i]);
printf("%d\n",mmss(S, n, m));
free(S);
}
return 0;
}