HDU1003-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 integers followed(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 there are more than one result, output the first one. Output a blank line between two cases.

Sample Input
25 6 -1 5 4 -77 0 6 -1 1 -6 7 -5
Sample Output
Case 1:14 1 4Case 2:7 1 6

AC之后写下思路冷静一下,感谢这个大佬,细致的讲解(HDU acm 1003 Max Sum || 动态规划求最大子序列和详解)

首先总结几个有关动态规划的特征:

1.程序的每一步处理数据,都是把当下所输入值,和之前所处理过的所有值进行处理,所以如果之前处理过的每一步都保存好判断依据(最重要的一部分),那么可以不用考虑每一步中所输入的数据,只需要留下校验标准就好。
2.这和递归的思想有些相似,都是把问题化简为:当下 和 之前。

然后来讲几个概念:

1.包含末项的子序列和有哪些:
比如 a[0] : a[0];
a[1] : a[0]+a[1] , a[1];
a[2] : a[0]+a[1]+a[2] , a[1]+a[2] , a[2]; … …
2.本题的核心便是这里包含末项的子序列最大和:
Maxsum a[0] = a[0];
Maxsum a[1] = max( a[0] + a[1] , a[1])
= max( maxsum a[0] + a[1], a[1]);
Maxsum a[2] = max( max( a[0] + a[1] + a[2] ,a[1] + a[2]) , a[2])
= max( max( a[0] +a[1], a[1]) + a[2] , a[2] ))
= max( maxsum a[1] + a[2] , a[2])
… …
由此我们可以很清晰地看到,我们成功把问题划分成, (之前 + 当下 ) 和 当下 进行取大值运算了,我觉得本题也因此被冠以“动态规划”问题。

上文提到,这种问题也可以近似看作递归,可以给出递归公式:
Maxsum a[n] = max( maxsum a[n-1] + a[n] , a[n])

疑点:只做“留尾”(或者说:后半段)的最大子序列和比较就能得出整个序列的最大子序列和吗?

答:其实可以联想99乘法表,难道我们每一纵列只需要罗列“留尾”(后半段)就可以把整表的数据表示出来吗?答案是肯定的,因为任何子序列都有“首”和“尾”,那么这个子序列一定从属于maxsum a[尾]之中。
所以我们每次运算出maxsum a[n],所得到的都是以 a[n]为结尾的最大子序列和,设置最大值(max)的存储变量,每次都与之比较,若大于max则更新max值,更新位置信息(见4)。

3.修改简化表达式,寻找程序主体:

Maxsum a[n] = max( maxsum a[n-1] + a[n] , a[n])
这个式子看起来仍然比较复杂,但是如果我们假设一种理想条件:maxsum a[n] 均为非负值,则上式的右侧比较大小中,可以很轻易地把他化简为:
Maxsum a[n] = maxsum a[n-1] + a[n]
(a>=0 <=> a + b >= b)
这下看起来整洁许多,下面我们探讨该理想条件(maxsum a[n] 均为非负值)的可行性:

  • 负数不可能作为maxsum a[n] 的结尾项

其实很简单,如果一个序列的最后一项(n)为负数,那么该序列的前(n-1)项和必然会大于该序列的所有项之和,所以如果a[n]为负数,我们便可以得知,maxsum a[n] = maxsum a[n-1](此时结尾项为a[n-1] ,印证上一结论),故有此可知,即便是最差情形,某序列的所有项均为负数,那么改maxsum a[n]应等于该序列首项a[0],或者说因为该序列持续递减,所以最大子序列应为首项,而首项的值早在输入第一个数的时候记录在了最大值(max)中,此时的maxsum a[0]重置为0对程序没有任何影响,于是我们可知,该理想条件(maxsum a[n] 均为非负值)可行。
如何实现maxsum a[n] 均为非负值?判断是否小于零,是则归零。
此时我们可以发现Maxsum a[n] = maxsum a[n-1] + a[n]该式作用可以简化为一句代码:
sum+=a;

4.更新位置信息
由(2)结尾处可知:sum > max时更新位置,由于sum的前身是Maxsum a[n],其末项便是位置的结尾,下面来讲开头: 由(3)所描述的可以知道sum 均为非负项,当其出现负数的情况时,我们会将其归零,此外,这个导致sum成为负数的尾项(n)也绝不能成为首项,我们自动将(n+1)设置为临时首项,当出现归零后(出现较大负数),若有sum>max(出现新最大子序列)时,我们再将临时首项提取出来。
但如果所有项都为负数呢?这时的最大子序列长度和起止位置均应是1,故我们要设置一个,输入第一个数字时的必执行项(初始化),此时将初始设置的临时首相 = 1, 提取为真实首项。
代码如下:

#include<stdio.h>  
2.#define PF printf  
3.#define SF scanf  
4.  
5.int main()  
6.{  
7.    int n, t, s, e, temp, i, maxsum, a, j, sum;  
8.  
9.    SF("%d",&n);  
10.    for(j = 1 ; j <= n; j++)  
11.    {  
12.        SF("%d",&t);  
13.        sum = s= e = 0;  
14.        maxsum = -1001;  
15.        temp = 1;  
16.  
17.        for(i = 1 ; i <= t ; i++)  
18.        {  
19.            SF("%d",&a);  
20.            sum+=a;  
21.  
22.            if(sum > maxsum)  
23.            {  
24.                maxsum = sum;  
25.                s = temp;  
26.                e = i;  
27.            }  
28.            if(sum < 0 )  
29.            {  
30.                sum = 0;  
31.                temp = i+1;  
32.            }  
33.  
34.        }  
35.        PF("Case %d:\n%d %d %d\n",j,maxsum,s,e);  
36.        if(j != n)  
37.            PF("\n");  
38.    }  
39.  
40.    return 0;  
41.} 

猜你喜欢

转载自blog.csdn.net/giggle66/article/details/79227375