基础DP+滚动数组---Max Sum Plus Plus HDU - 1024

Now I think you have got an AC in Ignatius.L's "Max Sum" problem. To be a brave ACMer, we always challenge ourselves to more difficult problems. Now you are faced with a more difficult problem.

Given a consecutive number sequence S 1, S 2, S 3, S 4 ... S x, ... S n (1 ≤ x ≤ n ≤ 1,000,000, -32768 ≤ S x ≤ 32767). We define a function sum(i, j) = S i + ... + S j (1 ≤ i ≤ j ≤ n).

Now given an integer m (m > 0), your task is to find m pairs of i and j which make sum(i 1, j 1) + sum(i 2, j 2) + sum(i 3, j 3) + ... + sum(i m, j m) maximal (i x ≤ i y ≤ j x or i x ≤ j y ≤ j x is not allowed).

But I`m lazy, I don't want to write a special-judge module, so you don't have to output m pairs of i and j, just output the maximal summation of sum(i x, j x)(1 ≤ x ≤ m) instead. ^_^

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 Input

1 3 1 2 3
2 6 -1 4 -2 3 -2 3

Sample Output

6
8

Hint

Huge input, scanf and dynamic programming is recommended.

解题思路参考自https://www.cnblogs.com/kuangbin/archive/2011/08/04/2127085.html

本题的大致意思为给定一个数组,求其分成m个不相交子段和最大值的问题。

设num为给定数组,n为数组中的元素总数,dp[i][j]表示前i个数在选取第i个数的前提下分成j段的最大值,其中1<=j<=i<=n && j<=m,状态转移方程为:

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

乍看一下这个方程挺吓人的,因为题中n的限定范围为1~1,000,000而m得限定范围没有给出,m只要稍微大一点就会爆内存。但仔细分析后就会发现 dp[ i ][ j ] 的求解只和 dp[ * ][ j ] 与  dp[ * ][ j-1 ] 有关所以本题只需要两个一维数组即可搞定状态转移。

在进行更进一步的分析还会发现其实 max(dp[ 0 ][ j-1 ]~dp[ i-1 ][ j-1 ]) 根本不需要单独求取。在求取now_Status(保存本次状态的数组)的过程中即可对pre_Status(保存前一次状态的数组)进行同步更新。

 。

总结dp[ i ][ j ] 表示前i个数在选取第i个数的前提下分成j段的最大值

          由于子段必须是连续的,所以 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。

还有就是   dp[n]是选取num[n]的前提下的值,所以dp[n]不一定是最大值,故需要一个ans来不断地保存最大值。
 
AC代码:
 
#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;
}

 

posted @ 2019-11-13 16:52  Litn  阅读(...)  评论(...编辑  收藏