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;
}
}