Week12—m子段和的最大值

问题描述

东东每个学期都会去寝室接受扫楼的任务,并清点每个寝室的人数。
每个寝室里面有ai个人(1<=i<=n)。从第i到第j个宿舍一共有sum(i,j)=a[i]+…+a[j]个人
这让宿管阿姨非常开心,并且让东东扫楼m次,每一次数第i到第j个宿舍sum(i,j)
问题是要找到sum(i1, j1) + … + sum(im,jm)的最大值。且ix <= iy <=jx和ix <= jy <=jx的情况是不被允许的。也就是说m段都不能相交。
注:1 ≤ i ≤ n ≤ 1e6 , -32768 ≤ ai ≤ 32767 人数可以为负数。。。。(1<=n<=1000000)
【输入】输入m,输入n。后面跟着输入n个ai
【输出】输出最大和
【样例输入】

1 3 1 2 3
2 6 -1 4 -2 3 -2 3

【样例输出】

6
8

Hint:数据量很大,需要scanf读入和dp处理。

问题分析

当m == 1 时:该问题就是最大子段和问题。
设dp[i]为以a[i]结尾的最大子段和,当我们考虑dp[i]的时候如果dp[i-1] > 0那么肯定把a[i]接在后面最优,否则,取a[i]最优。
得到 dp[i] = max(dp[i-1]+a[i],a[i]);
问题规模由2个决定,一是子段数m,二是元素个数n,准确的说是最后一个子段终止的标号。
在这里插入图片描述
如图,要求dp[ 3 ][ 6 ],只需比较他左边的那个,和上面那一行圈起来的之中最大的数,
再加上a[ j ] 即为dp[ 3 ][ 6 ] 的值。
优化一下:
1、沿着第m行的最后一个元素,往左上方向画一条线,线右上方的元素是没必要计算的
那么dp[ i ][ j ] ,j++的时候,j的上限为 i + n - m 即可。
还有左下角那一半矩阵,也是不用计算的,因为1个数字不可能分为2个子段
2、每确定一个dp[ i ][ j ],只需用到本行和上一行,所以不用开维数组也可以,省内存。
开两个一维数组,pre和dp,pre记录上一行,dp记录当前行

3、再对上一行红圈中的数字找最大值时,若用一个循环来找,容易超时。所以优化方法是:在每次计算dp之前,同时记录下j前面的最大元素。

#include <algorithm>
#include <cstdio>
#include <cstring>
using namespace std;
const int inf=0x7fffff;

int a[1000010];
int dp[1000010];
int premax[1000010];

int m,n,tmp;

int main()
{
    while(scanf("%d %d",&m,&n)!=EOF)
    {
    	memset(dp,0,sizeof(dp));
        memset(premax,0,sizeof(premax));
        for(int i=1;i<=n;i++)
            scanf("%d",&a[i]);
        for(int i=1;i<=m;i++)
        {
            tmp=-inf;
            for(int j=i;j<=n;j++)
            {
                dp[j]=max(dp[j-1]+a[j],premax[j-1]+a[j]);
                premax[j-1]=tmp;
                tmp=max(tmp,dp[j]);
            }
        }
        printf("%d\n",tmp);
    }
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值