题目的大意是酱紫:
输入两个数,m和n ,意思是n个数求m个不相交的(重读)子段,输出子段和的最大值。
举一个栗子:
2 6 // 6个数 2个不相交子段
-1 4 -2 3 -2 3
输出结果为 8 两个子段为{4},{3,-2,3};(当然这只是最大值其中一种,还可以是{4,3},{-2,3}等等)
主要思路:
首先num[1000005]存n个数字。
再来一个dp[1000005][1000005](肯定会爆内存,我们暂且先这么写)
这dp数组是个什么含义呢?dp[m][n]表示 以第n个数结尾的(重读)分成m个子段的和的最大值
当然我们的目标肯定不是dp[m][n],我们的目标是max{dp[m][t]}(1<=t<=n) 为啥呢??因为最后的结果说不定是哪个数结尾呢,所以我们找一遍以所有数为结尾的可能就好了。
转移方程:dp[i][j]=max ( dp[i][j-1] + num[j], dp[i-1][t] + num[j] ); i-1 <= t <= j-1;
(t的范围因为要分为i-1组所以一定是i-1个数开始到j-1个数)
转移方程怎么推导的? 以第j个数结尾分i段只有两种情况,?1. 前j-1个数已经有i段了,第j个数一定要差到第i段的末尾,于是这种可能就是dp[i][j-1]+num[j] ?2.已经分好了i-1段,j单独作为一段但是前i-1段不知道是前j-1个数中,几个数分出来的 ,所以循环找一下也就是dp[i-1][t]+num[j] (i-1 <= t <=j-1) 这个转移方程很重要一会儿优化要操作一下,那么到目前位置程序已经可以运行了,但是绝对爆内存,爆时间,内存就不说了,时间的话是O(n^3)得优化。。。笔者不才,可能有地方阐述不清。。
重点来了!!!!时间空间优化。
我们先从时间优化入手,由转移方程可以知道,在求dp[i][j]的时候要用到dp[i][j-1]和dp[i-1][t](i-1 <= t <=j-1)
这个dp[i-1][t]是个循环试想可不可以重新创建一个数组now[n],来吧这个循环取代掉
now[j-1]存上一层(i-1层)从0到j-1的最大值那么在求dp[i][j]的时候需要dp[i-1][t]的时候可以直接用now[j-1]替代
那么要求dp[i][j]只需要两个数据 1.dp[i][j-1] 2.now[j-1]。于是我们发现一个问题,需要的这两个数据和已经求好的dp[0~(i-1)]层没有任何关系,那还留着干嘛,所以可以直接将dp[m][n]优化为dp[m];
真·奥义·状态方程 !!! :
dp[j] = max (dp[j-1] , now[j-1]) + num[j];
那么问题来了,这个now数组怎么求???
我们可以在求dp[j](第i-1层的时侯)把now[j]记录下来,那么求第i层的时候就可以直接用了
设置一个maxn,如果求出来的dp【j】比maxn大,就更新掉maxn
for(int j=i;j<n;j++){
dp[j] = max (dp[j-1] , now[j-1]) + num[j];
if(maxn>dp[j])maxn=dp[j];
now[j]=maxn;
}
但是这样是不行的,为啥呢??因为求dp[j]的时候把now[j]更新了,但是当下一次循环也就是j+1的时候要用到now[j]但是now[j]已经不是原来的now[j]了,那怎么办??我们可以发现用完now[j-1]后这个数就没用了,下一次要用的是now[j]所以我们可以做个微小的变动,用完now[j-1]之后就更新掉他,就不会影响j+1的时候now[j]的使用了。。。美滋滋。。。
#include<iostream>
#include<string.h>
#include<stdio.h>
using namespace std;
int dp[1000005];
int num[1000005];
int now[1000005];
int main()
{
int n,m;
while(~scanf("%d%d",&m,&n))
{
for(int i=1; i<=n; i++)
scanf("%d",&num[i]);
memset(dp,0,sizeof(dp));
memset(now,0,sizeof(now));
int max1=-99999999;
for(int i=1; i<=m; i++)
{
max1=-99999999;
for(int j=i; j<=n; j++)
{
dp[j]=max(dp[j-1],now[j-1])+num[j];
now[j-1]=max1;
if(dp[j]>max1)max1=dp[j];
}
}
printf("%d\n",max1);
}
}