题目连接 HDU 1024
大概题意
有n个数字组成的一个数组,让你从中找出m个不相交子序列(要求连续),使这m个子序列的和最大。
解题思路
虽然题目中没有给m的范围,但是m是一定要小于n的(n<1000000)
我们先想一个比较朴素的算法:
dp[i][j]:意思是将前j个数字分为i段得到的最大和,一定要取第j个数
那么状态转移方程就是:dp[i][j]=max(dp[i][j-1]+a[j],max(dp[i-1][k])+a[j]);(0<k<j)
意思也就是:
1)前 j-1 个数已经形成了 i 段,那么第j个数直接加到第i段就可以了
2)前 j-1 个数形成了 i-1 段,那么我们要枚举是从1~j-1之间哪个数之前形成的i-1段的和是最大的,然后加上第j个数就好了
那么代码就应该是这样的:
for(int i=1; i<=m; i++)
{
for(int j=i; j<=n; j++)
{
for(int k=j-1; k>=1; k--)
{
dp[i][j]=max(dp[i][j],max(dp[i][j-1]+a[j],dp[i-1][k]+a[j]));
}
}
}
最后max(dp[m][i])[1<=i<=n]就是答案
但是哇,现在的代码来看我们的dp[1000000][1000000]要开这么大才行,可是空间是不被允许的,并且现在的代码有三重循环,时间上也是不被允许的。
我们可以发现最后一个循环的作用是找出最大的dp[i-1][k],也就是只和i-1这个状态有关,那么我们是不必要每次都循环一次的,只需要在上一个状态的时候记录一下就可以了,我们用一个数组maxx[k]来表示前 j-1 个数分成 i-1 组的最大值,那么这个时候的状态转移方程就变成了:
dp[i][j] = max ( dp[i][j-1] + a[j] , maxx[j-1] + a[j] );
这样的话我们的时间复杂度大概降到了O(n*logn)是差不多可以的,因为m<n所以到不了这么大,但是我们的空间复杂度依旧很高,是实现不了的。
我们发现我们的状态转移方程式是只和上一个状态有关系的,也就是分成i组的时候是只和 i-1 这个状态有关系的,并且dp[i][j]这个数组和 i-1 其实是没有关系的,只有maxx[] 和 i-1 有关系,所以我们可以把dp[i][j]这个数组给化简为dp[j],这样的话我们的空间也是允许的啦(- -撒花- -)
#include<stdio.h>
#include<string.h>
#include<algorithm>
using namespace std;
long long dp[1000005],maxx[1000005],a[1000005];
long long inf=9223372036854775806;
long long F(long long x,long long y)//long long用max()会有一些问题,所以开了一个外部函数
{
if(x>y)
return x;
return y;
}
int main()
{
int m,n;
while(~scanf("%d %d",&m,&n))
{
memset(dp,0,sizeof(dp));
memset(maxx,0,sizeof(maxx));
for(int i=1; i<=n; i++)
scanf("%lld",&a[i]);
long long mm;
for(int i=1; i<=m; i++)
{
mm=-inf;
for(int j=i; j<=n; j++)
{
dp[j]=F(dp[j-1]+a[j],maxx[j-1]+a[j]);
/*这里的dp[j]和dp[j-1]的前提都是分
为i段,dp[j]的意思是 在取第j个的情况下分为i段的最大和,
dp[j-1]的意思是在取第j-1个的情况下的最大和 */
maxx[j-1]=mm;//这里更新的是取第j-1个的情况下,前j-1个数分为i段的最大和
当数据要处理前j-1个数分为i+1段时,将会被用到
mm=F(mm,dp[j]);//更新最大值
}
}
printf("%lld\n",mm);
}
return 0;
}