【hdu 1024】Max Sum Plus Plus —— dp && 滚动数组

来源:互联网 发布:爱普生清零软件 编辑:程序博客网 时间:2024/05/03 20:15

题目链接:点击打开链接

这道题是最大子数组问题的扩展,首先回忆一下最大子数组的解决方法——tms,如果tms < 0那么我们将舍弃前面的部分重新开始累加最后得到ms,而这个问题拓展的地方在于——我们可以加断点,那么问题来了——在哪里加断点?

我们用i来表示子数组的组数,j表示数组元素个数。首先,i = 1时,好说,就是tms的方法,我们可以很轻易地解决,接下来看i = 2的情况,首先基本的方向一定是dp,这里采取递推的方法比较直接,而且和tms也可以挂钩,其次我们还可以得到一个结论,这时候dp[j]要从j = 2开始算起,因为j = 1时,一个元素分两组没什么意义,由此得到一个结论,第i轮dp的起点要从j = i开始算起。而且j = i时其实也没什么意义,它的结果就是前j个数的和,那么来看i = 2,j = 3时的一个例子。我们知道,最大子数组产生的原因在于数组中的负数元素,负数出现时会减少连续求和的结果,因此基本的最大子数组要做到的是使负数在这个子数组中对求和降低效果最小。推广到2组的情况,负数是决定是否添加断点的关键,因此举例时我引入负数:1,-2,2,dp[2] = -1,但当增加下一个元素时,我们显然不会将它归入前面的一组子数组,而是在负数处断开,使2成为新的子数组,这也是我们将要决策的问题——新添加的元素将归入前面的子数组,还是自成一组?

这已经来到了问题的关键,对于决策选项的前半段,我们发现它其实就是dp[j-1] + a[j],因为我们没有改动之前做好的划分;但后半段决策,我们将取消最后一组子数组的划分,成立新的子数组,对于i = 2的例子,我们将前j - 1元素看成了一个新的求一组的最大子数组的情况,也就是之前i = 1 时的dp[j-1] ,所以这里发现我们需要记录之前的结果了,因此将dp数组升维,dp[i][j]表示前j个元素(包括a[j])分成i个子数组和最大的结果,得到状态转移方程:

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

由于第i轮只是需要第i - 1轮的结果,因此我们可以做出下面的优化:开辟新的数组pre[]来记录之前一轮dp的结果,dp[]记录本轮的结果,这样又节省了空间。

因此新的状态转移方程为:dp[j] = max{ dp[j - 1] + a[j] , pre[j - 1] + a[j]}

但是还没有结束,我们只是讨论了新添加元素在划分子数组时的位置,这里默认了一个事情——新的元素是参与划分的,那么考虑之前负数的说法,如果这个数十负数又该怎么办呢?看新的例子:1,-2,2,-1,很显然了,我们舍弃了-1而仍然采取上一次的结果,所以我们再声明一个temp变量来记录上一次的结果,每次dp环节结束后将dp的结果与temp比较,较大者则为本次的最终结果即可,也就是result = max{temp , dp[j]},考虑到temp记录的就是上一轮的结果,所以直接temp = max{temp , dp[j]}

我来给一个打表的例子,,帮助加深理解。



最终代码如下:

#include <iostream>#include <cstring>#include <algorithm>const int N = 1000010;const int inf = 1000000000;using namespace std;int a[N];int dp[N], pre[N];int main(){int m, n,temp;while (scanf("%d%d", &m, &n) != EOF)  {for (int i = 1; i <= n; i++){scanf("%d", &a[i]);}memset(dp, 0, sizeof(dp));memset(pre, 0, sizeof(pre));for (int i = 1; i <= m; i++){temp = -inf;for (int j = i; j <= n; j++){dp[j] = max(dp[j - 1] + a[j], pre[j - 1] + a[j]);pre[j - 1] = temp;temp = max(temp, dp[j]);}}printf("%d\n", temp);}return 0;}


0 0
原创粉丝点击