InputEach test case will begin with two integers m and n, followed by n integers S 1, S 2, S 3 ... S n.
Process to the end of file.
OutputOutput the maximal summation described above in one line.
Sample Inputhtml
1 3 1 2 3 2 6 -1 4 -2 3 -2 3
Sample Outputc++
6 8
Hint数组
Huge input, scanf and dynamic programming is recommended.
解题思路参考自https://www.cnblogs.com/kuangbin/archive/2011/08/04/2127085.html
:
本题的大体意思为给定一个数组,求其分红m个不相交子段和最大值的问题。spa
设num为给定数组,n为数组中的元素总数,dp[i][j]表示前i个数在选取第i个数的前提下分红j段的最大值,其中1<=j<=i<=n && j<=m,状态转移方程为:code
dp[ i ][ j ] = max(dp[ i-1 ][ j ] + num[ i ],max( dp[ 0 ][ j-1 ]~dp[ i-1 ][ j-1 ]) + num[ i ])htm
乍看一下这个方程挺吓人的,由于题中n的限定范围为1~1,000,000而m得限定范围没有给出,m只要稍微大一点就会爆内存。但仔细分析后就会发现 dp[ i ][ j ] 的求解只和 dp[ * ][ j ] 与 dp[ * ][ j-1 ] 有关因此本题只须要两个一维数组便可搞定状态转移。blog
在进行更进一步的分析还会发现其实 max(dp[ 0 ][ j-1 ]~dp[ i-1 ][ j-1 ]) 根本不须要单独求取。在求取now_Status(保存本次状态的数组)的过程当中便可对pre_Status(保存前一次状态的数组)进行同步更新。内存
。ci
总结:dp[ i ][ j ] 表示前i个数在选取第i个数的前提下分红j段的最大值get
因为子段必须是连续的,因此 num[ i ]必须选取的前提下十分重要,不然 dp[ i-1 ][ j ] + num[ i ]不成立
方程解释:
(1) 当num[i]合并到上一个段时:dp[ i-1 ][ j ] + num[ i ]
(2)当num[i]做为独立的一段时(分段,此时上一个状态应为 j-1段 ):max( dp[ 0 ][ j-1 ]~dp[ i-1 ][ j-1 ]) + num[ i ]
在这个思路的基础上(每个状态的求取只与上一个状态有关),我将这两个一维数组换成滚动数组 dp[ i ][2] ,那么在在每次更新状态的时候对 j%2 即为 j 应在位置。
而后求 max( dp[ 0 ][ j-1 ]~dp[ i-1 ][ j-1 ]) 只须要用变量_max不断地求前缀最大值便可。
须要注意的是当 i 小于 j 时值不存在,dp的值 应置为-inf。
#include<bits/stdc++.h> using namespace std; typedef long long ll; const int maxn=1e6+2; #define inf 0x7fffffff ll dp[maxn][2]; //dp:保存当前状态和上一次状态的最大值 ll num[maxn]; int n,m; int main() { while(scanf("%d%d",&m,&n)==2){ dp[0][0]=0; dp[0][1]=0; for(int i=1;i<=n;i++){ scanf("%lld",&num[i]); dp[i][0]=0; dp[i][1]=0; } ll _max,ans; for(int j=1;j<=m;j++){ _max=-inf; ans=-inf; dp[j-1][j%2]=-inf; //注意当i小于j时值不存在,置为-inf for(int i=j;i<=n;i++){ _max=max(_max,dp[i-1][(j-1)%2]); //计算dp[1][j-1]~~dp[i-1][j-1]的最大值 dp[i][j%2]=max(dp[i-1][j%2]+num[i], _max+num[i]); //合并or分段 ans=max(ans,dp[i][j%2]);////由于dp[n]是选取num[n]的前提下的值,因此dp[n]不必定是最大值 } } printf("%lld\n",ans); } return 0; }