您的位置:首页 > 其它

杭电OJ——1024 Max Sum Plus Plus 详细分析+优化全过程

2013-01-27 22:58 330 查看
Problem Description

Now I think you have got an AC in Ignatius.L's "Max Sum" problem. To be a brave ACMer, we always challenge ourselves to more difficult problems. Now you are faced with a more difficult problem.

Given a consecutive number sequence S1, S2, S3, S4 ... Sx, ... Sn (1 ≤ x ≤ n ≤ 1,000,000, -32768 ≤ Sx ≤ 32767). We define
a function sum(i, j) = Si + ... + Sj (1 ≤ i ≤ j ≤ n).

Now given an integer m (m > 0), your task is to find m pairs of i and j which make sum(i1, j1) + sum(i2, j2) + sum(i3, j3) + ... + sum(im,
jm) maximal (ix ≤ iy ≤ jx or ix≤ jy ≤ jx is not allowed).

But I`m lazy, I don't want to write a special-judge module, so you don't have to output m pairs of i and j, just output the maximal summation of sum(ix, jx)(1 ≤ x ≤ m) instead. ^_^

 

Input

Each test case will begin with two integers m and n, followed by n integers S1, S2, S3 ... Sn.

Process to the end of file.

 

Output

Output the maximal summation described above in one line.

 

Sample Input

1 3 1 2 3
2 6 -1 4 -2 3 -2 3

 

Sample Output

6
8

Hint
Huge input, scanf and dynamic programming is recommended.

 

该题是经典的动态规划问题(m段子和的最大值),这道题我查阅了大概三四篇博客才完全搞定之,优化之后的算法堪称美妙绝伦!

首先,动态规划的老步骤,我们用dp[i][j]来表示问题的一种状态,定义如下

dp[i][j]: 以j结尾的i段子和的最大值

那么状态转移方程为

dp[i][j] = max{dp[i][j-1] + a[j], max{dp[i-1][t] (i-1<=t <= j-1) + a[j]}}

简要解释一下,在求dp[i][j]之前,假定dp[i][j-1]已经求得,即以j-1结尾的i段子和的最大值已知,那么在求以j结尾的i段子和的最大值的时候,首先j肯定是在第i段,这时,j只有两种情况,j要么自成一段(max{dp[i-1][t]
(i-1<=t <= j-1) + a[j]),要么和前面j-1个的数中的末尾几个数成一段(dp[i][j-1] + a[j]),这个状态转移方程应该不难理解

状态转移方程可以进一步优化

dp[i][j] = max{dp[i][j-1] , max{dp[i-1][t] (i-1<=t <= j-1) }} + a[j];

max里面还有max,比较讨厌,因此我们将它单独拎出来,令w[i][j] = max{dp[i][t](i<=t <=j)}

因此max{dp[i-1][t] (i-1<=t <= j-1) } = w[i-1][j-1]

因此

dp[i][j] = max{dp[i][j-1], w[i-1][j-1]}

w[i][j] = max{dp[i][t](i<=t <=j)} = max{dp[i][i], dp[i][i+1], ....,dp[i][j-1], dp[i][j]} = max{w[i][j-1], dp[i][j]}

最后的解为w[m]


至此,我们得到了两个关键的状态转移方程,并且,我们注意到每次求解dp[i][j]的时候,不会用到dp[i-1][...]的信息,因此,我们可以将此数组退化为一维数组,优化之后状态转移方程为

dp[j] = max{dp[j-1], w[i-1][j-1]}

w[i][j] = max{w[i][j-1], dp[j]}

仔细观察w数组,再次发现每一次求解dp数组和w数组,只用到了w数组的两行信息,因此,我们可以使用滚动数组继续优化w数组,将w退化成2xn数组

假定当前循环到第i层,用t来表示求解w第i行的值,1-t来表示上一次求得的w数组的信息,那么,我们初始化t为1,就能使得w的行数在0-1之间变化,w,见下面代码

int t = 1;
for (int i = 1; i <= m; ++i) {
w[t][i] = dp[i] = sum[i];
for (int j = i+1; j <= n; ++j) {
dp[j] = max(dp[j-1], w[1-t][j-1]) + a[j];
w[t][j] = max(dp[j], w[t][j-1]);
}
t = 1 - t; //这里表示为将此次求的的w数组成为下一次求w数组的前面一行,也就是i-1行
}


最后,注意边界条件 w[0][i] = 0;不难写出一下AC代码
#include <iostream>
#define MAX 1000010
using namespace std;
int w[2][MAX]
int dp[MAX];
int a[MAX];
int sum[MAX];

inline int max(int a, int b) {
return a > b ? a : b;
}
int main() {
int m, n, c;
while (scanf("%d%d", &m, &n) > 0) {
sum[0] = 0;
for (int i = 1; i <= n; ++i) {
scanf("%d", &a[i]);
sum[i] = sum[i-1] + a[i];
w[0][i] = 0;
}
int t = 1;
for (int i = 1; i <= m; ++i) {
w[t][i] = dp[i] = sum[i];
for (int j = i+1; j <= n-m+i; ++j) {
dp[j] = max(dp[j-1], w[1-t][j-1]) + a[j];
w[t][j] = max(dp[j], w[t][j-1]);
}
t = 1 - t;
}
printf("%d\n", w[m%2]
); //由于w退化了,没了w[m]
因此m%2可以表示第m次,你可以用m = 1,和2试试便知道了
}
return 0;
}

最后,关于j的循环终止值的优化作一点说明:
如果求解m-1段子和的子和,只需要求解到n-1个数

因为第n个数会在i的下一次循环中求到

那么[b]如果求解m-2段子和的子和,只需要求解到n-2个数[/b]

[b]。。。[/b]

[b]求解m-(m-i) = i段子和的子和,只需要求解到n-(m-i) = n-m+i个数
[/b]

[b]貌似这个只可意会,反正我觉得我有点说不清啊[/b]
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: