【DP优化】——hdu1024——改变拓扑序降维

                                  Max Sum Plus Plus

Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others)
Total Submission(s): 24897    Accepted Submission(s): 8555


Problem Description
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 S1, S2, S3, S4 ... Sx, ... Sn (1 ≤ x ≤ n ≤ 1,000,000, -32768 ≤ Sx ≤ 32767). We define a function sum(i, j) = Si + ... + Sj (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(i1, j1) + sum(i2, j2) + sum(i3, j3) + ... + sum(im, jm) maximal (ix ≤ iy ≤ jx or ix ≤ jy ≤ jx 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(ix, jx)(1 ≤ x ≤ m) instead. ^_^
 

 

Input
Each test case will begin with two integers m and n, followed by n integers S1, S2, S3 ... Sn.
Process to the end of file.
 

 

Output
Output 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.
 

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

思路:(转kuangbin)

设Num为给定数组,n为数组中的元素总数,Status[i][j]表示前i个数在选取第i个数的前提下分成j段的最大值,其中1<=j<=i<=n && j<=m,

状态转移方程为:

Status[i][j]=Max(Status[i-1][j]+Num[i],Max(Status[0][j-1]~Status[i-1][j-1])+Num[i])

乍看一下这个方程挺吓人的,因为题中n的限定范围为1~1,000,000而m得限定范围没有给出,m只要稍微大一点就会爆内存。

但仔细分析后

就会发现Status[i][j]的求解只和Status[*][j]与Status[*][j-1]有关(即满足滚动数组条件)

所以本题只需要两个一维数组即可搞定状态转移。

在进行更进一步的分析

还会发现其实Max(Status[0][j-1]~Status[i-1][j-1])根本不需要单独求取。

在求取now_Status(保存本次状态的数组)的过程中即可对pre_Status(保存前一次状态的数组)进行同步更新。

 

代码如下:

#include<iostream>
#include<string.h>
#include<stdio.h>
using namespace std;

const int maxn=1000100;
int n,m;

int a[maxn];//数列
//注意两个数组定义的细小差别
int dp[maxn];//dp[i]表示以第 i 个元素结尾 分成j个子串的最大和(包含a[i])
int mx[maxn];//mx[i]表示前 i 个元素分成 j 段可以构成的最大和(不一定有a[i])

int main()
{
    while(scanf("%d %d",&m,&n)!=EOF)
    {
        int tmp;

        for(int i=1;i<=n;i++)
            scanf("%d",&a[i]);
        memset(dp,0,sizeof(dp));
        memset(mx,0,sizeof(mx));

        for(int j=1;j<=m;j++)//分成 j 个子串
        {
            //当前状态即可确定子段数一致
            tmp=-0x7fffffff;//改变区间个数的时候才赋值
            for(int i=j;i<=n;i++)//前 i 个元素
            {
                //要么把a[i]放到前一个集合里,那就是从当前子段个数转移过来,子段个数不变
                //要么把a[i]令成一个新的子串,那就是从前一个子段个数转移过来,子段个数+1
                dp[i]=max(dp[i-1]+a[i] , mx[i-1]+a[i]);
                // i 个元素不可能分成 i+1 份
                mx[i-1]=tmp;
                //之后更新tmp值为当前位置j个子串的最优值
                tmp=max(tmp,dp[i]);
            }
        }

        printf("%d\n",tmp);
    }
    return 0;
}

 

题后感:

先说说0-1滚动,其实这就是两个数组放到一块了,反而不好理解

然后说说自己的一些感受:

动态规划果然非常考验思考能力,

这道题首先是分析,看到数组太大,就需要想到降维

(这里的降维不单是减掉一个方括号,降低一个维度的上限也算是降维)

然后是考虑降维后如何确保满足无后效性性,并找到最优子结构

 

这道题非常奇妙的一点就在于:通过改变拓扑序,利用 拓扑序的限制 去实现状态的正确转移

把相邻状态放在两个数组里保存,就实现了降维的目的。

posted @ 2016-07-14 19:46  琥珀川||雨露晨曦  阅读(92)  评论(0)    收藏  举报