题目大意:把一个数组划分成不相交的m段,使得这m段之和加起来最大。输出最大值。
这道题从题意的分析可知要使用动态规划来做
使用arr[j]来存放第j个数
dp[i][j]的含义:在包含arr[j]的前提下,前i段的最大值,那么我们可以分以下两种情况:
- 1、arr[j]不是第i段的第一个数,那么此时dp[i][j]就是dp[i][j-1]+arr[j]
- 2、arr[j]是第i段的第一个个数,那么此时dp[i][j]就是max(dp[i-1][k])+arr[j] (i-1<=k<=j-1)
解释一下为什么第二种情况要使用max(dp[i-1][k])+arr[j], i-1<=k<=j-1,首先,我们要知道dp[i][j]是表示在包含arr[j]的前提下,前i段的最大值,但是呢,如果arr[j]是负数,那么dp[i][j]肯定就不是表示前i段的最大和了,而前i段的最大和肯定是在之前出现的,同理dp[i-1][j-1]并不一定表示的前i-1段的最大和,但是前i-1段的最大和肯定是在i-1到j-1之间存在的,所以要使用dp[i-1][k], i-1<=k<=j-1
接下来我们就可以写出递推公式:
dp[i][j] = max(dp[i][j-1]+arr[j],max(dp[i-1][k])+arr[j]) i-1<=k<=j-1
但是上述的公式还有一个漏洞 也就是dp[m][n],dp[m][n]这个表示的时在包含arr[n]的前提下的最大值,但是我们前面分析过,包含arr[n]的不一定是最大的,所以说最后还要用一次max(dp[m][k]) m<=k<=n
上述的公式看起来已经能够解决这个问题了,但是其实还是不能解决,为什么? 由于题目中的m的范围并没有给出,而n的范围时1e6,所以按照上述方法做,最终肯定会爆内存
下面我们就采用滚动数组来进行空间优化:
首先我们来分析以下递推公式,递推公式中的dp[i-1][k] i-1<=k<=j-1,表示的是前面状态的最大和,而同时我们也不关心最大和到底是在哪一个位置取,那么我们就可以用pre[j-1]来表示前j-1个状态的最大和,此时递推公式就变成:
dp[i][j] = max(dp[i][j-1]+arr[j],pre[j-1]+arr[j])
我们会发现此时递推公式中只存在i了,那么此时我们就可以将i去除
最终的递推公式为:
dp[j] = max(dp[j-1]+arr[j],pre[j-1]+arr[j])
下面为AC代码:
#include <iostream>
#include <cstring>
#include <cstdio>
using namespace std;
const int MAXN = 1e6+5;
int n,m;
int dp[MAXN];
int pre[MAXN];
int arr[MAXN];
int main()
{
while(~scanf("%d%d",&m,&n))
{
memset(dp,0,sizeof(dp));
memset(pre,0,sizeof(pre));
for(int i=1;i<=n;++i)
scanf("%d",&arr[i]);
int maxNum;
for(int i=1;i<=m;++i)
{
maxNum = -0x3f3f3f3f;
for(int j=i;j<=n;++j)
{
dp[j] = max(dp[j-1]+arr[j],pre[j-1]+arr[j]);
pre[j-1] = maxNum;
maxNum = max(maxNum,dp[j]);
}
}
printf("%d\n",maxNum);
}
return 0;
}