[题解]HDU1024 Max Sum Plus Plus
前言
最近困惑于线性\(dp\)的定义——只有状态表示为一维的才叫线性\(dp\)吗?
通过CSDN上的这篇文章,我得到了答案:
所谓线性\(dp\),就是递推方程是有一个明显的线性关系的,一维线性和二维线性甚至多维都有可能。
动态规划里的每一个状态都是一个多维(\(1-n\)维)的状态。
比如说背包问题就是一个二维的问题,如果我们把它画出来的话会是一个二维矩阵的形式。
而我们在求的时候,有一个明显的求的顺序:即一行一行地来求。这样的有线性顺序的叫做线性\(dp\)。
正文
这道题是一道很巧妙的线性\(dp\)题,在上一篇文章——线性\(dp\)模型中也提到过,因为其前身其实就是上一篇写到的「最大连续子段和」。只不过这一题问的不是一段,而是\(m\)段,所以较上一题我们的选择更加自由。
(HDU注册上让验证手机号,结果愣是一直没收到验证码,而一天最多只能发\(3\)次,所以暂时没法测试自己的代码,但是目前和正解对拍是对的)
题意简述
多测,每次给你\(n\)个数,要求从中选出\(m\)个没有公共部分的连续子段,求这几个连续子段的和的最大值。
数据范围:\(1\leq m\leq n\leq 10^6\)(奈何\(m\)和数据组数具体范围都没给,因为\(m=10^3,n=10^6\)这个数量级就已经不可做了)
解题思路
用\(dp[i][j]\)表示以第\(j\)个元素结尾,分成\(i\)组的最大和\((i\leq j)\)。你可能会疑惑为什么不把\(i\)和\(j\)的含义反过来。别急,这是为了后期的优化。
我们用下列样例演示:
输入:
2 6
-1 4 -2 3 -2 3
输出:
8
如图,表示\(6\)个元素,取出\(2\)组的\(dp\ table\)(其实\(2\)行就够了,不过为了更好地演示我画到了\(n\))。
如图所示,递推式为\(dp[i][j]=max(dp[i][j-1],dp[i-1][k])+a[j](1\leq k<j)\),下面是推导过程:
- 上一个元素在当前组,因为在当前组,所以必须是连续的,即只有\(dp[i][j-1]\)。
- 上一个元素在上一组,而上一组可以和自己连续也可以不连续,所以有\(dp[i-1][k](1\leq k<j)\)。
- 上面两种情况取个最大值,别忘加上自己(即\(a[i]\))。
我们发现每个格子的状态只和当前行和上一行有关,所以我们可以优化空间为一维。而上一行以下标\(i\)结尾的最大值我们可以用\(maxx[i]\)表示。其实与滚动数组很像,但是滚动数组只是优化了空间。
我们这个做法除了优化空间,还可以节省时间。因为如果没有\(maxx\)的预处理,我们根据上面第二个步骤计算上一排的最大值时需要循环遍历,浪费时间。这样预处理,节省空间和时间,一举两得。
时间复杂度为\(O(Tnm)\)
*注意:可能需要输入优化(数据量比较大,题目中也有提到)
Code
点击查看代码
#include<bits/stdc++.h>
using namespace std;
int m,n,a[1000010],dp[1000010],maxx[1000010];
int main(){
ios::sync_with_stdio(false);
cin.tie(0);
while(cin>>m>>n){
for(int i=1;i<=n;i++){
cin>>a[i];
}
memset(dp,0,sizeof dp);
memset(maxx,0,sizeof maxx);
for(int i=1;i<=m;i++){
for(int j=i;j<=n;j++){
dp[j]=a[j]+max(dp[j-1],maxx[j-1]);
}
for(int j=i;j<=n;j++){
maxx[j]=max(maxx[j-1],dp[j]);
}
}
int ans=INT_MIN;
for(int j=m;j<=n;j++) ans=max(ans,dp[j]);
cout<<ans<<endl;
}
return 0;
}