HDU1003——Max Sum

来源:互联网 发布:腾讯数据统计 编辑:程序博客网 时间:2024/05/23 14:08

自打上次做了2084之后,尽管一直没再接触DP的题目,但是心里还是比较向往的。怎么说,尽管在小组中DP的部分没有被分给我,但是一点不知道也不好。这次接触了一个1003-Max Sum。

原题是给出一段序列,求其最大子序列的和以及起始和终止位置。下面是原题:

Problem Description

Given a sequence a[1],a[2],a[3]......a[n], your job is to calculate the max sum of a sub-sequence. For example, given (6,-1,5,4,-7),the max sum in this sequence is 6 + (-1) + 5 + 4 = 14.

Input

The first line of the input contains an integer T(1<=T<=20) which means the number of test cases. Then T lines follow,each line starts with a number N(1<=N<=100000), then N integersfollowed(all the integers are between -1000 and 1000).

Output

For each test case, you should output two lines. The first line is "Case #:", # means the number of the test case. The second line contains three integers, the Max Sum in the sequence, the start position of the sub-sequence, the end position of the sub-sequence. If thereare more than one result, output the first one. Output a blank line between two cases.

(样例略)

最初的时候我并不认为这是一个DP题目。于是我天真地写了一个最简单易懂的代码——简单地将每组子序列都计算出来再逐一比较:

#include<cstdio>using namespacestd; int t, n, ai, mem[100000], bestans=-1000000000, position[2], tempbest[3]; int main(){    scanf("%d", &t);    for(int i=0; i<t; i++){        scanf("%d", &n);        scanf("%d", &ai);        mem[0]=ai, bestans=ai, position[0]=1,position[1]=1;        for(int j=1; j<n; j++){            scanf("%d", &ai);            mem[j]=ai, tempbest[0]=ai,tempbest[1]=j+1, tempbest[2]=j+1;            for(int k=j-1; k>-1; k--){                if((mem[k]+ai) >= tempbest[0]){                    tempbest[0]=mem[k]+ai;                    tempbest[1]=k+1;                    tempbest[2]=j+1;                }                mem[k]+=ai;            }            if(tempbest[0] > bestans){                bestans=tempbest[0];                position[0]=tempbest[1];                position[1]=tempbest[2];            }        }        printf("Case %d:\n%d %d %d\n", i+1, bestans, position[0], position[1]);        if(t != n-1)printf("\n");    }    return 0;}

然而OJ无情的给了我一个TLE。我一看,这样做的复杂度就是O(n^2),不超时才怪……于是我又转回问题分析过程,然后……发现这个问题是可以被简化的。从第二个数据开始每次输入一个数据后,就会有两个新的子序列生成,然而生成的子序列的和一旦比其他同终点的子序列的和小,就会被淘汰掉。(注意:长度即数字个数,下同)

第一次,输入序列中第一个数后诞生了一个相对于部分序列(长度为1)的最大子序和;
而第二次,在序列中第二个数输入后出现了两个新序列,它们其中一个是前一个部分序列的最大子序和与第二个数的和。这两个新的子序列和中必有一个被淘汰,而另一个和再与前一个部分序列的最大子序和比较,从而得到一个相对于长度为2的部分序列的最大子序和;

第三次和前面第二次的过程几乎一样,只是得到的结果是相对于长度为3的部分序列的最大子序和……

以此类推,当第n个(最后一个)数据读入后,经过常数次运算即可得到相对于长度为n的部分序列(整个序列)的最大子序和。

至此,应该不难看出,此题是由重叠的子问题构成的;每一个子问题相对于其他问题是独立的,因为对于每一个子问题,它们都不需要考虑其他子问题的求解过程;每一个子问题都有一个对于该问题的独一无二的最优解,所以具有最优子结构;当序列长度为1时,其最大子序和有唯一确定的答案,即边界

于是我此时大悟,这是个DP。

关于备忘,每次只需记录下上一个最大部分子序列的值和它的开始/终止位置即可。

看一下时间。对于每组输入测试用例,所需的时间复杂度是O(n),在接受范围之内。

接下来是状态转移方程。

(注:mem记录前一个最大子序列和的值;tempbestpos数组存储每次的淘汰结果:[0]为值,[1]、[2]分别是起点、终点;bestans储存目前得到的最大子序和,position数组记录该最大子序和的起点和终点,mem == 0 和 ai == 0 是为配合代码输出“第一种可能”添加的条件)

if((mem+ai > ai) || (mem == 0)){     tempbestpos[0]=mem+=ai;     tempbestpos[2]=j+1;}else if((mem+ai < ai) || (ai == 0)){    tempbestpos[0]=mem=ai;    tempbestpos[1]=tempbestpos[2]=j+1;}if(tempbestpos[0] > bestans){     bestans=tempbestpos[0];     position[0]=tempbestpos[1];     position[1]=tempbestpos[2];}


于是代码如下:

#include<cstdio>using namespace std; int t, n, ai, mem, bestans=-1000, position[2], temppos[3], tempbestpos[3]; int main(){    scanf("%d", &t);    for(int i=0; i<t; i++){        scanf("%d", &n);        scanf("%d", &ai);        mem=bestans=ai,tempbestpos[1]=position[0]=1, tempbestpos[2]=position[1]=1;        for(int j=1; j<n; j++){            scanf("%d", &ai);            if((mem+ai > ai) || (mem == 0)){                tempbestpos[0]=mem+=ai;                tempbestpos[2]=j+1;            }            else if((mem+ai < ai) || (ai ==0)){                tempbestpos[0]=mem=ai;               tempbestpos[1]=tempbestpos[2]=j+1;            }            if(tempbestpos[0] > bestans){                bestans=tempbestpos[0];                position[0]=tempbestpos[1];                position[1]=tempbestpos[2];            }        }        printf("Case %d:\n%d %d %d\n", i+1, bestans, position[0], position[1]);        if(i != t-1)printf("\n");    }    return 0;}

0 0