Max Sum Plus Plus

题意:求m个不相交子串,使得这些子串的和最大;

/*
1.基本思路:
  首先,定义数组num[n],dp[m][n].
  num[n]用来存储n个整数组成的序列.
  dp[i][j]用来表示由前 j项得到的含i个字段的最大值,且最后一个字段以num[j]项结尾。仔细想想,我们可以知道:
  dp[i][j]=max(dp[i][j-1]+num[j],dp(i-1,t)+num[j])   其中i-1<=t<=j-1.
  (因为必须是以 num[j] 结尾的,所以num[j]一定属于最后一个子段,即要么自己独立成一个子段,要么与前边以num[j-1]结尾的子段联合)
  所求的最后结果为 max( dp[m][j] ) 其中1<=j<=n.
  但是,我们会发现,当n非常大时,这个算法的时间复杂度和空间复杂度是非常高的,时间复杂度近似为O(m*n^2),
  空间复杂度近似为O(m*n).因此,我们需要优化算法来降低时间复杂度和空间复杂度.
2.优化算法:
  (1)节省时间
  由基本思路,我们可以知道,dp[i][j]=max(dp[i][j-1]+num[j],dp(i-1,t)+num[j]),其中i-1<=t<=j-1.我们只要找到dp[i][j-1]
  和dp[i-1][t]的最大值加上num[j]即为dp[i][j].所以,定义一个数组pre_max[n],用pre_max[j-1]来表示求解dp[i][j]时dp[i-1][t]
  的最大值,则dp[i][j]=max(pre_max[j-1],dp[i][j-1])+num[j].
  特别注意,pre_max[n]这个位置的存储空间是始终用不到的,因此可以用来存储其他数值,在接下来会用到。
  在求解dp[i][j]的同时,我们可以计算出dp[i][t];i<=t<=j的最大值,这个最大值在计算dp[i+1][j+1]的时候需要作为pre_max[j]的
  形式被使用,我们先把它存在pre_max[n]中。
  你可能会问:为什么不把它直接放在pre_max[j]中呢?因为你接下来需要计算dp[i][j+1]的值,需要用到pre_max[j]中原来的值,
  如果你把它存在这里,就会覆盖掉计算dp[i][j+1]所需要的那个值。所以,先把它放在pre_max[n]中。
  当我们计算完dp[i][j+1]之后,就会发现pre_max[j]中的值已经没有用处了,我们可以把它更新为计算dp[i+1][j+1]所需要的那个值,
  即之前放在pre_max[n]中的那个值,即执行pre_max[j]=pre_max[n].
  这样我们就节省了计算最大值时付出的时间代价。



思路:设dp[i][j]表示前j个数(包括第j个数),分成i段的最大和,那么它的最优子结构可能来
自两部分,第一部分是当第j个数单独作为1段,第二部分是j和前面的连成一部分。那么
dp[i][j]=max(dp[i-1][j]+a[j],dp[i-1][k]+a[j])(k>=i-1&&k<=j-1)
再设g[i][j]表示前j个数,分成i段的最大和(此时第j个数可能包括也可能不包括)
g[i][j]=max(g[i][j-1],dp[i][j]);
通过g函数的定义,可以将dp的递推式优化为dp[i][j]=max(dp[i][j-1],g[i-1][j-1])+a[j];
g[i-1][j-1]表示的是,第j个单独成段,去掉第j个后,剩下的i-1段由剩下的j-1个数组成;
滚动数组:因为i从1到m,要计算dp[i][j]=max(dp[i][j-1],g[i-1][j-1])+a[j],我们只需要知道它
的前一轮数就行,而g[i-1]是前一轮推出来的,所以可以把i的数组直接滚动了。(能不能滚动,主
要是看计算当前状态需要的之前状态是什么,如果只需要前一轮的状态,那么只要保证在需要它之
前,它还没有被更新就行)

*/
//dp[i][n]=max(dp[i-1][n]+a[i],dp[i-1 ][n-1]+a[i])

#include <iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define inf 0x3f3f3f3f

using namespace std;
int a[1000005];
int dp[1000005];
int ma[1000005];

int main()
{
    int m,n,ans;
    while(scanf("%d%d",&m,&n)!=EOF)
    {
        for(int i=0;i<n;i++)
        {
            scanf("%d",&a[i]);
        }
        memset(dp,0,sizeof(dp));
        memset(ma,0,sizeof(ma));
        for(int j=0;j<m;j++)
        {
            ans=-inf;//确保比每个数都小
            for(int i=j;i<n;i++)
            {
                dp[i]=max(dp[i-1]+a[i],ma[i-1]+a[i]);//ma为上一组dp的最优解,所以优化采用滚动数组
                //表达dp[i][n]=max(dp[i-1][n]+a[i],dp[i-1][t]+a[i])
                //dp[i-1]+a[i]  dp[i-1][n]+a[i]
                //ma[i-1]+a[i]  dp[i-1][t]+a[i]
                ///printf("dp[%d]=%d dp[%d]=%d ma[%d]=%d\n",i,dp[i],i-1,dp[i-1],i-1,ma[i-1]);
                
                ma[i-1]=ans;//滚动数组,ma为求下一组dp的最优解
                ///printf("ma[%d]=%d\n",i-1,ma[i-1]);
                ans=max(ans,dp[i]);//判断最大值,解释为啥一开始ans为无穷小
                //求出当前循环所能得到的最优解
                ///printf("ans=%d\n\n",ans);
            }
        }cout <<ans<< endl;
    }


    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值