hdu 1024 dp 解题报告

本文探讨了一个典型的DP问题——将n个数分成m段以获得最大和,并详细阐述了解决该问题的方法,包括递推公式的推导、时间复杂度从O(n^3)到O(n^2)的优化过程及空间复杂度的降低。

http://acm.hdu.edu.cn/showproblem.php?pid=1024

自己也是没想出怎么做,看了解题报告。

做完之后对DP能够有更深的理解。

求n个数分为m段的最大和。

首先比较难想到的是递推公式。

假设j个数分为i段,求最大和,第j个数为a[ j ],当前状态为dp[ i ] [ j]。并且我们假定第j个数被分在最后一段中。

那么,可以分为两种情况:  1.第j个数与其前面的几个数共同成段  2.第j个数单独成段

对于第一种情况,dp[ i ][j] = dp[ i ] [ j - 1 ] + a[ j ]。 对于第二种情况 dp[ i ][ j ] = max( dp[ i - 1 ] [ t ] ) + a[ j ]  , ( i - 1 <= t <= j - 1 )

由于是求最大,所以可得  dp[ i ] [ j ] = max(dp[ i ]  [ j - 1],max( dp[ i - 1][ t ] )  ) + a[ j ]

接下来做第一次压缩:

我们设法省去 max( dp[ i - 1][ t ] )  ,( i - 1 <= t <= j - 1 )       对 t 的遍历。 只要在计算dp[i - 1][ ] 对j遍历时 保存 每个 j 值 所对应的 max( dp[ i - 1][ t ] )  于pre[ i ] [ j ] 中

所以我们这样做:

对于每个i,我们遍历j并且计算dp[ i ] [ j ] 的值,用于计算dp[ i ] [ j+1]。 

并且保存每个j值所对应的当前 max( dp[ i ] [ t ] ) , ( i  <= t <= j  )  于pre[ i ]  [ j ] ,用于计算 dp[ i + 1]  [ j ]

	for(i = 1; i <= m; i++)
        {
            maxx = -100000000;//注意对于每个i都要初始化 maxx
            for(j = i; j <= n; j++)
            {
                 dp[i][j] = max(dp[i][j - 1],pre[i - 1][j]  ) + a[j];
		 maxx = max( maxx, dp[i][j]);  //注意这一行和上面一行 的顺序,不能对调。 因为max( dp[i][t])中 t的上界 是 j - 1。如果对调,上界变成 j 
            }
        }

上面的这次压缩是对时间的压缩,时间复杂度从 o(n^3) 变为 o(n^2)。

然后我们对空间进行压缩:

我们发现,对于一个特定的i ,计算 dp[ i ][  ] 时,我们只用到 dp[ i ][  ] ,而和 dp[ i - 1][  ],dp[ i - 2][  ]的值都无关 。

同时我们只用到  pre[i - 1][  ] ,而和 pre[ i - 2 ],pre[ i - 3 ] 都无关。

所以我们每次只要 保存 dp[ i ][ ] 这一组值,在计算dp[ i + 1][ ]时,就已经可以舍弃 dp[ i ][ ]了。

同样的我们只要保存 pre[ i - 1 ][  ] 这一组值就可以,计算dp[ i + 1] 时 ,就已经可以舍弃pre[ i - 1 ][  ] 了。

即,计算dp[ 3 ][  ] 时 舍弃 dp[ 2 ][ ]保存dp[ 3 ][ ],计算dp[ 4 ][  ] 时 舍弃 dp[ 3 ][ ]保存dp[ 4 ][ ]。

我们可以看到,每次保存的只是 一组数,dp根本没必要使用二维数组了。同样的,pre也不用二维数组了。

用dp[ j ]保存当前状态,在计算dp[ j + 1] 时调用,在遍历到下一个 i 时,用下一个dp[ j ]覆盖。

pre[ j ] 保存最大值,在计算dp[ j + 1 ] 时使用,在下一个i时 覆盖。

#include<stdio.h>
#include<math.h>
#include<stdlib.h>
#include<string.h>
#define maxn 1000003
int a[maxn],m,n,dp[maxn],pr[maxn]; 

int max(int a,int b)
{
    return a>b?a:b;
} 
int main()
{
    int i,j,maxx;
    while(~scanf("%d%d",&m,&n))
    {
        memset(pr,0,sizeof(pr));
        for(i = 1; i <= n; i++)
        {
            scanf("%d",&a[i]);
        }
        for(i = 1; i <= m; i++)
        {
            maxx = -100000000;
            for(j = i; j <= n; j++)
            {
                dp[j] = max(pr[j - 1],dp[j - 1]) + a[j];
                pr[j - 1] = maxx;
                maxx = max(maxx,dp[j]); 
            }
        }
        printf("%d\n",maxx);
    }
    return 0;
} 


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值