hdu 1024 Max Sum Plus Plus(最大M子段和)

    题意:给出一个长度为n的序列,求它的m个子序列的和的最大值。

    思路:用j表示子序列的个数,用i表示计算进行到的位置,参考最大子段和(最大连续子序列)的解法,很容易得到这样一个转移方程:dp[j][i]=Max(dp[j-1][i-1]+a[i],dp[j][i-1]+a[i]);即到第i个元素,j个序列的最大值可以有到第i-1个元素,j-1个序列的最大值和j个序列的最大值加上当前元素得到。听着就有一种漏洞百出的感觉。

    最大M子段和跟最大子段和不同的一个地方是,在求dp[j][i]的值得时候,如果(i>j),那么dp[j-1][i-1]中是可以不包含第i-1个元素的,因为dp[j][i]可以由dp[j-1][i-1]+a[i]得到,当前元素此时表示为一个单独的序列。又由于dp[j][i]可能由dp[j][i-1]+a[i]得到,在这种情况下,dp[j][i-1]中就必须包含第i-1个元素。

    我解决的方法就是分两种情况讨论,即用dp[j][i][0]表示不必包含当前元素的情况,用dp[j][i][1]表示必须包含当前元素的情况。这时候可以得到一组状态转移方程:dp[j][i][0]=Max(dp[j-1][i-1][0]+a[i],dp[j][i-1][1]+a[i],dp[j][i-1][0]);dp[j][1]=Max(dp[j-1][i-1][0]+a[i],dp[j-1][i-1][1]+a[i],dp[j][i-1][1]+a[i]);。把这个状态转移方程放入代码中提交,结果是MLE。

    观察这个状态转移方程,发现当前的状态均是由上一组数据的状态转化而来,这个地方很容易让人联想到可以使用滚动数组优化,去掉中间表示元素坐标的一维。又考虑到状态转移方程均是由较小的j-1个序列或者j个序列的状态推出j个序列的状态,所以可以将序列状态从大到小循环,这样就避免了滚动数组循环赋值的花费。之后得到了这样一份代码:

#include<stdio.h>
#include<string.h>
#define N 1000005
#define M 1005
typedef __int64 LL;
int a[N];
LL dp[M][2];
int main()
{
    int m,n;
    while(scanf("%d%d",&m,&n)!=EOF)
    {
        for(int i=1; i<=n; i++)
            scanf("%d",&a[i]);
        memset(dp,0,sizeof(dp));
        for(int i=1; i<=m; i++)
        {
            dp[i][0]=dp[i][1]=dp[i-1][0]+a[i];
            for(int j=i-1; j>=1; j--)
            {
                ///dp[j][0]=Max(Max(dp[j-1][0]+a[i],dp[j][1]+a[i]),dp[j][0]);
                if(dp[j-1][0]+a[i]>dp[j][1]+a[i])
                    dp[j][0]=dp[j-1][0]+a[i]>dp[j][0]?dp[j-1][0]+a[i]:dp[j][0];
                else
                    dp[j][0]=dp[j][1]+a[i]>dp[j][0]?dp[j][1]+a[i]:dp[j][0];
                ///dp[j][1]=Max(Max(dp[j-1][0]+a[i],dp[j-1][1]+a[i]),dp[j][1]+a[i]);
                if(dp[j-1][0]+a[i]>dp[j-1][1]+a[i])
                    dp[j][1]=dp[j-1][0]+a[i]>dp[j][1]+a[i]?dp[j-1][0]+a[i]:dp[j][1]+a[i];
                else
                    dp[j][1]=dp[j-1][1]+a[i]>dp[j][1]+a[i]?dp[j-1][1]+a[i]:dp[j][1]+a[i];
            }
        }
        LL max=dp[m][0];
        for(int i=m+1; i<=n; i++)
        {
            for(int j=m; j>=1; j--)
            {
                ///dp[j][0]=Max(Max(dp[j-1][0]+a[i],dp[j][1]+a[i]),dp[j][0]);
                if(dp[j-1][0]+a[i]>dp[j][1]+a[i])
                    dp[j][0]=dp[j-1][0]+a[i]>dp[j][0]?dp[j-1][0]+a[i]:dp[j][0];
                else
                    dp[j][0]=dp[j][1]+a[i]>dp[j][0]?dp[j][1]+a[i]:dp[j][0];
                ///dp[j][1]=Max(Max(dp[j-1][0]+a[i],dp[j-1][1]+a[i]),dp[j][1]+a[i]);
                if(dp[j-1][0]+a[i]>dp[j-1][1]+a[i])
                    dp[j][1]=dp[j-1][0]+a[i]>dp[j][1]+a[i]?dp[j-1][0]+a[i]:dp[j][1]+a[i];
                else
                    dp[j][1]=dp[j-1][1]+a[i]>dp[j][1]+a[i]?dp[j-1][1]+a[i]:dp[j][1]+a[i];
            }
            max=dp[m][0]>max?dp[m][0]:max;
        }
        printf("%I64d\n",max);
    }
    return 0;
}
   

    提交之后AC。需要注意的是,在这份代码中状态转移方程不能调用Max函数,否则就会超时。

    用if,else语句来选取三个元素中的较大值,代码看着非常不美观,而且感觉这三种情况中有一些是重合的。

    首先是dp[j][1]这种情况,因为我用0表示的是不必连续的,用1表示必须连续的,那么dp[j][0]考虑的状态中已经包含了dp[j][1]的状态,即dp[j-1][0]一定是大于等于dp[j-1][1]的,这时就不用考虑dp[j-1][1]+a[i]的方案,可以得到dp[j][1]=Max(dp[j][1]+a[i],dp[j-1][0]+a[i])。

    再来考虑dp[j][0]的状况,可以看出dp[j][1]+a[i]和dp[j-1][0]+a[i]的状态在求dp[j][1]的时候已经考虑过了,如果先求dp[j][1]的话,可以将这两种状态合并为一种状态,可以得到dp[j][0]=Max(dp[j][1],dp[j][0])。

    因为之前有dp[j][0]不一定包含当前元素的推论,再加上dp[j][0]大于等于dp[j][1]的推论,可以得出dp[m][0]就是最优解的推论,这样的话,max也可以省掉了。修改之后的代码如下:

#include<stdio.h>
#include<string.h>
#define N 1000005
#define M 1005
typedef __int64 LL;
int a[N];
LL dp[M][2];
int main()
{
    int m,n;
    while(scanf("%d%d",&m,&n)!=EOF)
    {
        for(int i=1; i<=n; i++)
            scanf("%d",&a[i]);
        memset(dp,0,sizeof(dp));
        for(int i=1; i<=m; i++)
        {
            dp[i][0]=dp[i][1]=dp[i-1][0]+a[i];
            for(int j=i-1; j>=1; j--)
            {
                dp[j][1]=(dp[j-1][0]>dp[j][1]?dp[j-1][0]:dp[j][1])+a[i];
                dp[j][0]=dp[j][1]>dp[j][0]?dp[j][1]:dp[j][0];
            }
        }
        for(int i=m+1; i<=n; i++)
        {
            for(int j=m; j>=1; j--)
            {
                dp[j][1]=(dp[j-1][0]>dp[j][1]?dp[j-1][0]:dp[j][1])+a[i];
                dp[j][0]=dp[j][1]>dp[j][0]?dp[j][1]:dp[j][0];
            }
        }
        printf("%I64d\n",dp[m][0]);
    }
    return 0;
}
 

   提交265msAC。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值