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

来源:互联网 发布:php pre match 编辑:程序博客网 时间:2024/05/17 06:34
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 32 6 -1 4 -2 3 -2 3
 

Sample Output
68
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][n]

至此,我们得到了两个关键的状态转移方程,并且,我们注意到每次求解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 1000010using 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][n]); //由于w退化了,没了w[m][n]因此m%2可以表示第m次,你可以用m = 1,和2试试便知道了  }  return 0;}

最后,关于j的循环终止值的优化作一点说明:

如果求解m-1段子和的子和,只需要求解到n-1个数

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

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

。。。

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

貌似这个只可意会,反正我觉得我有点说不清啊

原创粉丝点击