题意:
给你n个数,然后让你在里面找到m个子序列,让这m个子序列的和最大。其中可以不要一些数,但是这些子序列里面的数必须是连续的。
题解:
这道题是我做kuangbin大神的专题的第一道题,当然就被吓到不敢去做,今天用了快一天的时间把这道题给理解了,以下是我看的博客:
http://blog.sina.com.cn/s/blog_677a3eb30100jxqa.html
http://blog.csdn.net/u013187393/article/details/42914165
http://blog.csdn.net/u013761036/article/details/39804595
按照顺序来看比较好,好了,再在我这里说一下我的见解吧,
这道题是最大子序列的和的加强版,比最大子序列恶心的多了,我之前的想法还停留在01背包的层次,就是拿或者不拿的思维层次,发现在这里行不通。所以学了一整天,然后我说一下公式:
dp[i][j]=max(dp[i][j-1]+a[j],dp[i-1][k]+a[j]),(其中i-1<=k<=j)。dp[i][j]就是把j个数分成i段的最大和,而dp[i][j-1]+a[j]就是把a[j]分配到第i段中,而后面的dp[i-1][k]+a[j]的意思就是把前面的j-1个数分成i-1段,而a[j]变成第i段的第一个数值。有可能说到这里很多人不明白,这个k到底是怎么回事吧?我们来看一下例子吧:
例如有一组数据 2 3 3 -7 2
那么它的最大值是多少呢?答案是5,怎么得出来的呢?
假如前面的dp值我们都已经计算出了,并且都是正确的,那么dp[2][3]=max(dp[2][2]+a[3],dp[1][k]+a[3]),我们可以看出dp[2][2]为-4,那么这里k怎么算呢?因为限制在i-1<=k<=j-1即是1<=k<=2中了,那么我们看一下k=1的时候dp[1][1]为3,而k=2的时候dp[1][2]为-4,所以我们这时候当然是让k=1了,带入式子中就可以得出结果5了。为什么k是取i-1<=k<=j-1的范围呢?因为有时候a[j-1]可能是非常大的负数,大到使前面的正数值总和(我们位了好理解这里假设前面的总和算出来是正数)加上该数也为负,你觉得你还会要它吗?显然不能,除非第i段缺少数值,才会要它,不到万不得已是不会要它的。
然后这里的m因为没说出范围所以我们要优化一下空间,即是dp[i][j]变成dp[j]表示的是第j个数时的最大值,以上是我的见解,剩下的例如时间上的优化可以看我发上面的三个博客和我的代码注释就好了。
#include<stdio.h>
#include<string.h>
#include<algorithm>
using namespace std;
#define LL long long int
#define INF 0x7fffffff
const int MAXN= 1000000+7;
LL dp[MAXN];//因为不知道m是多少所以优化成了一维的了。
LL maxk[MAXN];//保存的是分成几段的最大值。
LL a[MAXN];
int main()
{
int n,m;
while(~scanf("%d%d",&m,&n))
{
for(int i=1;i<=n;i++)
{
scanf("%lld",&a[i]);
dp[i]=0;
maxk[i]=0;
}
LL MAX=-INF;
for(int i=1;i<=m;i++)
{
MAX=-INF;
for(int j=i;j<=n;j++)//为什么是从i开始呢?因为你跑过了一遍分成i=1段之后,第i=1段就应该是最优的状态了,所以不用再跑了,直接当做最优的状态来参与分成i=2阶段的计算。
{
dp[j]=max(dp[j-1]+a[j],maxk[j-1]+a[j]);//这里就相当于dp[i][j]=max(dp[i][j-1]+a[j],dp[i-1][k]+a[j])。
maxk[j-1]=MAX;//这里保存的是dp[i-1][k]的值,即将k分成i-1段得到的最大值(i-1<=k<j-1). 还有就是不能更新mxk[j],只能更新j-1是因为更新j就会被当前的这个子序列更新的时候用到。
MAX=max(dp[j],MAX);//保存最大值。
}
}
printf("%lld\n",MAX);//还有就是这里不能写成dp[n],因为dp[j]代表的是到第j个的时候的最大和。例如你计算到最后,有可能不要最后一个数比要最后一个数要大。所以这里要输出MAX。
}
}