Hdu-1024 Max Sum Plus Plus 详解+滚动数组优化

Max Sum Plus Plus

Now I think you have got an AC in Ignatius.L’s “Max Sum” problem. To be a brave ACMer, we always challenge ourselves to more difficult problems. Now you are faced with a more difficult problem.

Given a consecutive number sequence S 1, S 2, S 3, S 4 … S x, … S n (1 ≤ x ≤ n ≤ 1,000,000, -32768 ≤ S x ≤ 32767). We define a function sum(i, j) = S i + … + S j (1 ≤ i ≤ j ≤ n).

Now given an integer m (m > 0), your task is to find m pairs of i and j which make sum(i 1, j 1) + sum(i 2, j 2) + sum(i 3, j 3) + … + sum(i m, j m) maximal (i x ≤ i y ≤ j x or i x ≤ j y ≤ j x is not allowed).

But I`m lazy, I don’t want to write a special-judge module, so you don’t have to output m pairs of i and j, just output the maximal summation of sum(i x, j x)(1 ≤ x ≤ m) instead. _
Input
Each test case will begin with two integers m and n, followed by n integers S 1, S 2, S 3 … S n.
Process to the end of file.
Output
Output the maximal summation described above in one line.
Sample Input
1 3 1 2 3
2 6 -1 4 -2 3 -2 3
Sample Output
6
8

问题描述
现在,我认为您已经在Ignatius.L的“最大和”问题中获得了AC。作为一个勇敢的ACMer,我们总是挑战自己,迎接更棘手的问题。现在,您面临着一个更困难的问题。
给定一个连续编号序列S 1,S 2,S 3,S 4内容S X,内容S Ñ(1≤X≤N≤1,000,000,-32768≤小号X ≤32767)。我们定义一个函数sum(i,j)= S i + … + S j(1≤i≤j≤n)。
现在给定一个整数m(m> 0),您的任务是查找m和i对和j对,它们使sum(i 1,j 1)+ sum(i 2,j 2)+总和(I 3,J 3)+ … + SUM(im,Jm)最大(I x ≤iý ≤Ĵ X或I X ≤Ĵ ý ≤Ĵ X是不允许的)。
但是我很懒,我不想编写一个特殊的判断模块,所以您不必输出m对i和j,只需输出sum(i x,j x)(1 ≤x≤m)。^ _ ^

输入值
每个测试用例将以两个整数m和n开头,然后是n个整数S 1,S 2,S 3 … S n。
处理到文件末尾。

输出量
一行输出上述最大和。

题意:将一个长度为n的数组取出m组,并且让这m个分块后的数组相加后值和是最大的,m个分块不能相交。

首先长度为n
第一种情况:它要求长度为n取出m组,并且最大。那么前n-1个分成m组也是最大的(因为每个m,n都要满足),这时长度还缺一个,那么我加上第n个数就行。
第二种情况:它要求长度为n取出m组,并且最大。那么前n个分成m-1组也是最大的,这时还缺一组,那么让第n个数单独成为一个组,再加上这个组就有m组了。

所以
动态规划方程: dp[i][j]:表示前 j 个数分成 i 组时的最大和
dp[i][j]=max(dp[i][j-1]+s[j],dp[i-1][k]+s[j])

其中k等于i-1到j-1,
对于k的下限,不能小于i-1的原因是dp[i-1][k]是第一种情况,前k 个数分成i-1组了,k不管是多大,我已经分成i-1组了,我只需要加上第j个数形成第i组就行。但是k不能乱取,难道有前2个数分成3组的情况吗(dp[2][3])? 但是前两个数分成2组是可以的,所以k的下限是i-1。
对于k的上限不能等于j,因为要求的是前j个数的i个分组,如果k=j,那不就等于前j个数分成i-1组了吗,再加上第个j数就成了前j+1个数分成i组的最大值了。所以要小于j。

这是二维的dp

 	for(int i=1;i<=m;i++){
 		   for(int j=i;j<=n;j++){
 				  int tmp=-INF;
 				 for(int k=i-1;k<j;k++){
 				     tmp=max(dp[i-1][k],tmp);  //第二张情况,分成i-1组时最大值
 				  }
 					 dp[i][j]=max(tmp+s[j],dp[i][j-1]+s[j]);
 			 }
 	}
    int Max=-INF;
 	for(int i=1;i<=n;i++){
 	  Max=max(Max,dp[m][i]);
 	}
 	return Max;
 	
答案不为dp[m][n],因为可能长度比n小分成m组的和是比长度为n分成m组的和要大的,因为s[j]是存在负数的


但是二维存在一个问题,那就是数组开不下,因为它的最大n为1000000,dp[n][n]是开不下的
所以只能采用一维滚动数组的优化
回想一下01背包中的滚动数组,当循环次数在第i时,dp的值还存的是i-1的值
对于二维的dp[i][j-1]自然能在第i次循环就能得到.
但是dp[i-1][k]是i-1次循环时最大的,那只需要记录下i-1时的最大值时就行.

滚动数组优化

int lastmax[maxn];   //记录i-1时长度为的最大值
int tmp;
   for(int i=1;i<=m;i++){
		   tmp=-INF;
		 	  for(int j=i;j<=n;j++){
						dp[j]=max(dp[j-1]+s[j],lastmax[j-1]+s[j]);
						lastmax[j-1]=tmp;
						tmp=max(tmp,dp[j]);
				}
	 }
	 
记录最大值要使用数组进行记录,原因是如果只使用一个变量lastmax记录,会发现它存的是前n个数分成i-1组的最大值,但是二维里面只是前k个数(i-1<k<j-1)分成i-1组的最大值,所以用一个变量记录不对,要使用数组对应起来


#include <iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<vector>
#include<fstream>
using namespace std;

const int maxn = 1000000;
const int INF=0x3f3f3f3f;


int s[maxn];
int m, n;
//int dp[10000][10000];
int dp[maxn];
int lastmax[maxn];

void init() {
	memset(dp, 0, sizeof(dp));
	memset(lastmax,0,sizeof(lastmax));
}

// int solve() {
// 	for(int i=1;i<=m;i++){
// 		   for(int j=i;j<=n;j++){
// 				  int tmp=-INF;
// 				 for(int k=i-1;k<j;k++){
// 				     tmp=max(dp[i-1][k],tmp);
// 					 }
// 					 dp[i][j]=max(tmp+s[j],dp[i][j-1]+s[j]);
// 			 }
// 	}
// 	int Max=-INF;
// 	for(int i=1;i<=n;i++){
// 	  Max=max(Max,dp[m][i]);
// 	}
 //	return Max;
// }


int solve(){
	int tmp;
   for(int i=1;i<=m;i++){
		   tmp=-INF;
		 	  for(int j=i;j<=n;j++){
						dp[j]=max(dp[j-1]+s[j],lastmax[j-1]+s[j]);
						lastmax[j-1]=tmp;
						tmp=max(tmp,dp[j]);
				}
	 }
	 return tmp;  //不能是dp[n]因为前n个数分成m组不一定是最大的,因为s[j]可能是负数
}


int main() {

	while(cin >> m >> n){
	init();
	for (int i = 1;i <=n;i++) {
		cin >> s[i];
	}
	cout << solve() <<endl;
}
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值