题目描述

给出一个长度为 n 的序列 a,选出其中连续且非空的一段使得这段和最大。

输入格式

第一行是一个整数,表示序列的长度 n。
第二行有 n 个整数,第 i个整数表示序列的第 i个数字 \(a_i\)

输出格式

输出一行一个整数表示答案。

输入输出样例

输入

7
2 -4 3 -1 2 -4 3

输出

4

数据范围

对于 40% 的数据,保证 n \(\leq\) 2 \(\times\) \(10^3\)
对于 100% 的数据,保证 n \(\leq\) 2 \(\times\) \(10^5\),-\(10^4\) \(\leq\) \(a_i\) \(\leq\) \(10^4\)

思路1(暴力求解):

计算出每个子序列的和再选出其中最大的那一个。(注意:根据这道题目的数据范围来说,这个思路会TLE)

#include<iostream>
#include<vector>
using namespace std;
typedef long long int ll;
int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);
    int n;
    cin>>n;
    vector<int> a(n+5),s(n+5);
    for(int i=1;i<=n;i++)
    {
         cin>>a[i];
         s[i]=s[i-1]+a[i];//前缀和处理一下
    }
    ll maxSum=a[1];
    for(int i=1;i<=n;i++)//i代表子序列的左端点
    {
        for(int j=i;j<=n;j++)//j代表子序列的右端点
           if(s[j]-s[i-1]>maxSum)//s[j]-s[i-1]=a[i]+a[i+1]+...+a[j]
           maxSum=s[j]-s[i-1];
    }
    cout<<maxSum<<endl;
    return 0;
}

思路2(分治思想):

将一个序列分成两段,分别求两个子序列的最大子序列和,再求出越过序列分界点的最大子序列和,那么序列的最大子序列和一定是三个值中的最大值。
1)如何求两个子序列的最大子序列和?
答:将这两个子序列分别再分成两段,当子序列中只有一个数字的时候最大子列和就是这个数字,再递归返回就可以了。
2)如何求出越过序列分界点的最大子序列和?
答:从分界点左端扫描,维护出一个leftMax;同理,右端维护出一个rightMax。leftMax+分界点处的值+rightMax即为所求。

#include<iostream>
#include<algorithm>
using namespace std;
typedef long long int ll;
int a[200005];
ll cal(int l,int r)
{
    if(l>=r)
    return a[l];
    int mid=(l+r)>>1;
    ll leftMax=0,rightMax=0,thisSum=0;
    for(int i=mid-1;i>=l;i--)
    {
        thisSum+=a[i];
        if(thisSum>leftMax)
        leftMax=thisSum;
    }
    thisSum=0;
    for(int i=mid+1;i<=r;i++)
    {
        thisSum+=a[i];
        if(thisSum>rightMax)
        rightMax=thisSum;
    }
    return max(max(cal(l,mid),cal(mid+1,r)),leftMax+a[mid]+rightMax);//求出cal(l,mid),cal(mid+1,r),leftMax+a[mid]+rightMax三者之间的最大值
}
int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);
    int n;
    cin>>n;
    for(int i=1;i<=n;i++)
    {
         cin>>a[i];
    }
    cout<<cal(1,n)<<endl;
    return 0;
}

思路3(动态规划):

根据题意我们可以得出以下结论:
1)第一个数字为有效序列。
2)如果一个数加上上一个有效序列得到的结果比这个数大(即前面的子列和>0),那么该数也属于这个有效序列。
3)如果一个数加上上一个有效序列得到的结果比这个数小(即前面的子列和<0),那么这个数单独成为一个新的有效序列,我们需要将前面的子列和抛弃,即重新归0。
综上,我们可以得出以下代码:

#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
typedef long long int ll;
int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);
    int n,x;
    ll thisSum=0,maxSum=-10005;
    cin>>n;
    while(n--)
    {
        cin>>x;
        thisSum+=x;//向右累加
        maxSum=max(maxSum,thisSum);//发现更大和则立即更新当前结果
        if(thisSum<0)//如果当前子列和为负,
        thisSum=0;//则不可能使后面的子列和更大,抛弃它
        
    }
    cout<<maxSum<<endl;
    return 0;
}