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]);
}
}