http://acm.hdu.edu.cn/showproblem.php?pid=1024
题意:有一个含n个数的序列,找到m个子序列,使这m个子序列的和最大。1 ≤ x ≤ n ≤ 1,000,000, -32768 ≤ Sx ≤ 32767
分析:先用状态 dp[ i ][ j ] 表示前 j 个数取出 i 段所得到的最大值。 可以知道,对于下一个数,有以下三种操作:
1、不取这个数 。
2、取这个数并当作上一段的尾巴。
3、取这个数并当作新一段的头部。
对于这三种操作,状态转移方程为:
dp[ i ][ j ]=max ( dp[ i ][ j-1 ] , dp[ i ][ j-1 ]+num[ j ] , max( dp[ i-1 ][ t ] ) + num[ j ] ) (1 < k < j)
接下来,这个 n 的范围为1e6,而这个算法的时间、空间复杂度都不行,因此需要优化。(其中一个不选的操作可以将状态看作选择第 j 个然后每次记录ans)
①:为什么要优化呢?
我们来看数据范围:0<m<=n<1000000
对于上面的做法,首先需要跑i:1->m表示分成1~m这些段,接着跑j:0->n表示j个数字分成i段,再接着跑t:i-1->n寻找max(dp[i-1][t]),
可以近似的认为时间复杂度为:O(m*n^2),肯定超时。空间复杂度开二维肯定也爆了。
②怎么优化呢?
时间上:我们发现对于max(dp[i-1][t]),我们其实只要在前面的过程中把最大的记录在一个数组pre[t]中,那么就可以不用跑一遍循环而可以直接使用。
空间上:因为最终的转态都是由一个个小状态逐步转换而来的,而转态是由最小的开始的,所以对于dp[i][j]我们可以优化为一维数组dp[j],
因为我们在跑for(i:1->m)时是从1开始跑的,当跑到2时dp[j]中的数据就是dp[1][j]中的数据,我们可以直接使用。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> using namespace std; const int INF=0x3f3f3f3f; const int maxn=1000009; int a[maxn],dp[maxn],pre[maxn]; // dp[i][j]=max(dp[i][j-1]+a[j],dp[i-1][k]+a[j]) i-1<=k<=j-1 空间上,二维的不行,时间上,m*i*i,不行 int main(){ //解决方法,滚动数组 + 加一个数组 int n,m,ans; while(scanf("%d%d",&m,&n)!=EOF){ for(int i=1;i<=n;i++){ scanf("%d",a+i); } memset(dp,0,sizeof(dp)); memset(pre,0,sizeof(pre)); for(int i=1;i<=m;i++){ ans=-INF; for(int j=i;j<=n;j++){ //这里是 j=i !!意思是前i个数分成i段 dp[j]=max(dp[j-1]+a[j],pre[j-1]+a[j]); pre[j-1]=ans; //这里特意的倒序是为了实现滚动数组 ans=max(ans,dp[j]); //保存最大值 //这里也存在一个倒序 (因为是j-1) } } printf("%d ",ans); } return 0; }