【书上讲解】最大m段子段和问题

描述

【题解】


设f[i][j]表示前i个数字分成了j段的最大子段和。
则f[i][j] = max(f[i-1][j]+a[i] (第i个数字和第j段合在一起),f[k][j-1]+a[i] (第i个数字作为第j段的第一个数字,同时在j-1段的情况中找到和最大的那个))
这样的时间复杂度是\(O(m*n^2)\)的,代码的写法在代码1中
接下来我们做一些优化。
首先把数组的第二维改成滚动数组。
然后令F[i][j]=max(f[1..i][j]);
这样在做第二层的转移的时候f[i][j]就能直接用F[i-1][j-1]做转移了
当然也不用非得再重开一个新的数组。
在f[i][j]做完转移之后把它改成前缀的最大值就行。(注意一定要做完转移之后再改变,因为f[i][j]在转移的时候用到了f[i-1][j])
这样在做转移的时候就少掉了O(N)的一次枚举了
复杂度变成O(n*m)的了.详见代码2

【代码1】

#include <cstdio>
#include <algorithm>
using namespace std;

const int N = 1e6;
const int M = 30;

int f[N+10][M+10],a[N+10];
int n,m;

int main(){
    while (~scanf("%d%d",&m,&n)){
        for (int i = 1;i <=n;i++) scanf("%d",&a[i]);
        for(int l = 1;l <= m;l++){
            for (int i = l;i <= n;i++){
                if (i==l){
                    f[i][l] = f[i-1][l-1]+a[i];
                }else{
                    f[i][l] = f[i-1][l]+a[i];//和第l段合并
                    //printf("%d ",f[i][l]);
                    //自己独立成段
                    for (int k = l-1;k <= i-1;k++)
                        f[i][l] = max(f[i][l],f[k][l-1]+a[i]);
                }
            }
        }
        int ans = f[m][m];
        for (int i = m+1;i <= n;i++) ans = max(ans,f[i][m]);
        printf("%d\n",ans);
    }
    return 0;
}

【代码2】

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;

const int N = 1e6;
const int M = 30;

int f[N+10][2],a[N+10];
int n,m;

int main(){
    while (~scanf("%d%d",&m,&n)){
        for (int i = 1;i <=n;i++) scanf("%d",&a[i]);
        memset(f,0,sizeof f);
        for(int l = 1;l <= m;l++){
            for (int i = l;i <= n;i++){
                if (i==l){
                    f[i][l&1] = f[i-1][(l-1)&1]+a[i];
                }else{
                    f[i][l&1] = f[i-1][l&1]+a[i];//和第l段合并
                    //printf("%d ",f[i][l]);
                    //自己独立成段
                    f[i][l&1] = max(f[i][l&1],f[i-1][(l-1)&1]+a[i]);
                }
            }
            for (int i = l+1;i <= n;i++)
                f[i][l&1] = max(f[i][l&1],f[i-1][l&1]);
        }
        printf("%d\n",f[n][m&1]);
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值