题意:
东东每个学期都会去寝室接受扫楼的任务,并清点每个寝室的人数。
每个寝室里面有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)
Input
输入m,输入n。后面跟着输入n个ai
Output
输出最大和
Sample Input
1 3 1 2 3
2 6 -1 4 -2 3 -2 3
Sample Output
6
8
Hint
数据量很大,需要scanf读入和dp处理。
思路:
题目大意:输入一个m,n分别表示成m组,一共有n个数即将n个数分成m组,m组的和加起来得到最大值并输出。
动态规划,借助矩阵可以直观的看到计算过程。
定义二维数组dp
, dp[ i ][ j ]
,表示前 j 项所构成 i 子段的最大和,且必须包含着第j项,即以第j项结尾
然后是一个递推过程。
求dp[ i ][ j ]
,有两种情况
1、dp[ i ][ j ] = dp[ i ] [ j-1 ] + a[ j ]
,即把第j项融合到第 j-1 项的子段中,子段数没变
2、dp[ i ][ j ] = dp[ i-1 ] [ t ] + a[ j ],(i-1<= t < j )
,把第 j 项作为单独的一个子段,然后找一下i-1个子段时,最大的和,然后加上a[ j ]
然后比较上面两种情况,取大的。
但是题目的数据量比较到,时间复杂度为n^3,n<=1000000,显然会超时,继续优化。
下面看图,红色数字为输入的序列:
如图,要求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前面的最大元素。
时间复杂度大致为O(m*(n-m+1)),mn-m方
通过图片,分析情况1和2,就能发现,从左上角走到第 m 行的最后一个元素即可,找出第 m 行的最大值即为答案。
代码:
#include <algorithm>
#include <cstdio>
#include <cstring>
using namespace std;
const int inf=0x7fffff;
const int maxn=1e6+10;
int a[maxn];
int dp[maxn];
int premax[maxn];//max(dp[i-1][k])就是上一组0~j-1的最大值
int m,n,tmp;
int main()
{
while(scanf("%d %d",&m,&n)!=EOF)
{
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
}
memset(dp,0,sizeof(dp));
memset(premax,0,sizeof(premax));
for(int i=1;i<=m;i++)
{
tmp=-inf;
for(int j=i;j<=n;j++)
{
//premax[j-1]目前代表的是分成i-1组前j-1个数的最大值,a[j]单独一组组成i组
//dp[j-1]代表j-1个数分成组,第j个数a[j]放在前面i组的一组中,两种方式选取较大者
dp[j]=max(dp[j-1]+a[j],premax[j-1]+a[j]);
premax[j-1]=tmp;//当前考虑的是j但是temp是上一次循环得到的,所以更新的是j-1
tmp=max(tmp,dp[j]);//更新答案,这样下次循环同样更新的是j-1
}
//这样也就更新得到了分i组的max,下次分i+1组的时候就可以使用了
}
printf("%d\n",tmp);
}
return 0;
}