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. ^_^
Process to the end of file.
1 3 1 2 3 2 6 -1 4 -2 3 -2 3
6 8HintHuge 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 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][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个数
貌似这个只可意会,反正我觉得我有点说不清啊