基础DP A - Max Sum Plus Plus (有趣的递推)

A - Max Sum Plus Plus

题目链接

ps:感觉这是暑假集训到现在遇到的最变态最难理解的题目了。。。刚刚接触动态规划,实在不懂怎么写,就看了大佬的题解。

本题参考博文:https://blog.csdn.net/lishuhuakai/article/details/8067474

题意:

给出一个有n个数字的序列,要求从序列中取出m个段,数字可以不全部使用。要求所有段的和取最大。n 的最大可取1000000

输入: 

第一个样例输入了一个1代表m,输入一个3代表n,接下来输入n(3)个数
第二个样例输入了一个2代表m,输入一个6代表n,接下来输入n(6)个数
1 3 1 2 3
2 6 -1 4 -2 3 -2 3

输出:

6
8

思路:

可以用两个数组。dp [ i ] [ j ] 代表将 j 个数分 i 段得到的最大值。w [ i ] [ j ] 代表将 j 个数分 i 段,并且第 j 个数必须要使用的情况下,得到的最大值。

那么可以得到以下公式:

w [ i ] [ j ] = max ( dp [ i - 1 ] [ j - 1 ] + num [ j ] , w [ i ] [ j  - 1 ] + num [ j ] )

必须使用最后一个数的话,前者把这个数列为一个新的段 , 后者为把它加入前面的那个段中去。

dp [ i ] [ j ] = max ( w [ i ] [ j ] , dp [ i ] [ j - 1 ] )

对于此处的数,无非就是加入与不加入的关系,前者为加入,后者为不加入。

那么来一个二重for循环就可以得出答案了。

for ( int i = 1 ; i <= m ; i++)
    for (int j = i ; j <= n ; j++)
        if (i == j) dp[i][j] = sum[j];
        else
        {
            w[i][j] = max (dp[i - 1][j - 1] + num[j] , w[i][j  - 1] + num[j]);
            dp[i][j] = max (dp[i][j - 1] , w[i][j]);
        }

在 j 的循环中,j 是从 i 开始的 ,因为如果数字的数量还没有段的数量多,显然是没有意义的。

因为在 i == j 时,显然每个数都各自为一个段,所以此处的dp就是前 j 个数的和。可以在输入n个数时用到一个

sum [ maxn ] 数组来储存。

但是由于 n 的最大值是1000000,所以显然这样做是行不通的。(因为二维数组开不出来)

优化

首先我们可以发现,w数组一直取的是前一位,所以说可以转化为一维的 w [ maxn ] , w [ j ] 。

再看 dp 数组 ,实际上需要使用的一直是 dp [ i - 1 ][ j - 1 ] 以及 dp [ i [[ j - 1 ] 。所以说在dp的第一维上并不需要maxn这么大,只需要2,用于储存 i 与 i - 1 就足够了。此处我们会使用一个 t 用于交替使用两个维度的 dp 在下面的代码中可以很容易看懂。

再看 num 与 sum 事实上我们也只需要保存 sum 即可 ,因为 num [ i ] 可以通过 sum [ i ] - sum [ i - 1] 得到。可以省下许多空间。

代码如下。

int t = 1 ;
        for (int i = 1 ; i <= m ; i++)
        {
             for (int j = i ; j <= n ; j++)
                if (i == j)
                    dp[t][j] = w[j] = sum[j];
                else
                {
                    w[j] = max ( dp[1 - t][j - 1] , w[j - 1]) + sum[j] - sum[j - 1];
                    dp[t][j] = max (dp[t][j - 1] , w[j]);
                }
            t = 1 - t ;
        }

 

 完整代码如下:

#include <stdio.h>
using namespace std;


const int maxn = 1e6 + 10;
int sum[maxn];
int dp[2][maxn];
int w[maxn];

int max (int a , int b)
{
    if (a > b) return a;
    else return b;
}
int main ()
{
    int m , n;

    while (scanf ("%d %d" , &m , &n) != EOF)
    {
        sum[0] = 0;
        int k;
        for (int i = 1 ; i <= n ; i++)
        {
            scanf ("%d" , &k);
            sum[i] = sum[i - 1] + k;
            dp[0][i] = 0;
        }

        int t = 1 ;
        for (int i = 1 ; i <= m ; i++)
        {
             for (int j = i ; j <= n ; j++)
                if (i == j)
                    dp[t][j] = w[j] = sum[j];
                else
                {
                    w[j] = max ( dp[1 - t][j - 1] , w[j - 1]) + sum[j] - sum[j - 1];
                    dp[t][j] = max (dp[t][j - 1] , w[j]);
                }
            t = 1 - t ;
        }

        printf ("%d\n"  , dp[m % 2][n]);
    }
}

 

评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值