集训第五周 动态规划 最大子段和

A - 最大子段和
Time Limit:1000MS     Memory Limit:32768KB     64bit IO Format:%I64d & %I64u
Submit Status

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

2 5 6 -1 5 4 -7 7 0 6 -1 1 -6 7 -5
 

Sample Output

Case 1: 14 1 4
Case 2: 7 1 6
 
这是一个动态规划题,可使用dp[i]表示以a[i]结尾的的最大子段和,那么这就是一个子结构,如何保证其最优性呢?
可列出状态转移方程为dp[i]=max(dp[i-1]+a[i],a[i]),如果前面的子段和是负数,那么就无需加上它,因为这对于a[i]来说是一种拖累
 
#include <iostream>
#include <cstdio>
using namespace std;
const int Max=1e5+10;
const int inf=0x3f3f3f3f;
struct node
{
    int l,val;
}dp[Max];
int main()
{
    int T,ca=1;
    for(scanf("%d",&T);T;T--)
    {
        int n,tmp,ans,pre,post;
        ans=-inf;
        scanf("%d",&n);
        dp[0].val=0;dp[0].l=1;
        for(int i=1;i<=n;i++)
        {
            scanf("%d",&tmp);
            if(dp[i-1].val+tmp>=tmp)
            {
                dp[i].val=dp[i-1].val+tmp;
                dp[i].l=dp[i-1].l;
            }
            else
            {
                dp[i].val=tmp;
                dp[i].l=i;
            }
            if(dp[i].val>ans)
            {
                ans=dp[i].val;
                pre=dp[i].l;
                post=i;
            }
        }
        printf("Case %d:
%d %d %d
",ca++,ans,pre,post);
        if(T-1) puts("");
    }
    return 0;
}
View Code

尽管这道题可以使用dp方程形式去解决,也可以用一般方法去观察

6 -1 5 4 -7

sum     ans

6          6

5          5

10        10

14        14

7          7

我们把数列从i=1处累加,每次取出ai处的累加值,最后得到了数列的最大子段和14,然而这样的方法并非总是正确的,例如

-2 5 -10 7 7 4

sum    ans

-2       -2

3        3

-7      -7

0        0

7        7

11      11

很显然,答案是18而非11,说明累加并不一定是从i=1开始的,假如程序能够察觉应该从i=4处开始累加,那么答案就会正确了

那么程序需要实现两个功能,起点更新和答案更新。(这样之所以正确在于它枚举了所有区间,类似于尺取法)

起点的特性是什么呢?那个sum值不能是负数,正确性显然,如果加上一个负数值反而会变小,注意并不是说ai不能为负数,如(6 -1 5)

由此可知,当一个sum值为负时,就应该更新起点,把sum值置为0。然而应该把起点位置置为此时的i吗?

答案是NO,注意,由于累加的特性,此时的ai就是导致累加值为负的罪魁,(此时的ai肯定也是负数),如果将它算进去,就违背了之前的讨论。

还存在一个问题:起点更新和答案更新应该谁前谁后?

考虑到一种情况,此时的答案是负数,如果起点更新在答案更新之前,那么相当于人为地增加了1个0作为错误干扰信息,使得答案变为0

所以答案更新必须在起点更新之前,这一点在写dp时一样重要

#include <iostream>
#include <cstdio>
using namespace std;
const int Max=1e5+10;
const int inf=0x3f3f3f3f;
int main()
{
    int T,ca=1;
    for(scanf("%d",&T);T;T--)
    {
        int n,tmp,ans,sum,pre,anpre,anpost;
        ans=-inf;
        scanf("%d",&n);
        pre=1;sum=0;
        for(int i=1;i<=n;i++)
        {
            scanf("%d",&tmp);
            sum+=tmp;
            if(sum>ans)
            {
                ans=sum;
                anpost=i;
                anpre=pre;
            }
            if(sum<0)
            {
                sum=0;
                pre=i+1;
            }
        }
        printf("Case %d:
%d %d %d
",ca++,ans,anpre,anpost);
        if(T-1) puts("");
    }
    return 0;
}
View Code
原文地址:https://www.cnblogs.com/zsyacm666666/p/4723485.html