基础dp Max Subsring Plus HDU-1024 最大字串升级版之最大多字串(滚动数组中的倒序细节)

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\n",ans);
    }
    return 0;
}

 

posted @ 2019-09-01 00:51  *Zzz  阅读(242)  评论(0编辑  收藏  举报