最大子段和

N个整数组成的序列a[1],a[2],a[3],…,a[n], 求该序列如a[i]+a[i+1]+…+a[j]的连续子段和的最大值。当所给的整数均为负数时和为0。
例如:-2,11,-4,13,-5,-2,和最大的子段为:11,-4,13。和为20。


1.最大子段和问题的简单算法

用数组a[]存储给定的n个整数a1,a2,……,an。

#include<iostream>
#include<cmath>
#include<algorithm>
#include<cstring>
using namespace std;
#define Max 50005
int a[Max];
int MaxSum(int n,int * a)
{
    int sum = 0;
    for(int i = 0;i <= n;i ++)
    for(int j = i;j <= n;j ++)
    {
        int thissum = 0;
        for(int k = i;k <= j;k ++)
        thissum += a[k];
        if(thissum > sum)
        {
            sum = thissum;
            //besti = i;
            //bestj = j;
        }
    }
    return sum;
}
int main()
{
    int n;
    cin >> n;
    for(int i = 0;i < n;i ++)
    cin >> a[i];
    cout << MaxSum(n,a) << endl;
    return 0;
}

从这个算法的3个for循环可以看出,它所需要的计算时间是O(n^3)。事实上,如果注意到a[i]到a[j]的和= a[j]+ a[i]到a[j - 1]的和,则可将算法中的最后一个for循环省去,避免重复计算,从而使算法得以改进。改进后的代码为:

#include<iostream>
#include<cmath>
#include<algorithm>
#include<cstring>
using namespace std;
#define Max 50005
int a[Max];
int MaxSum(int n,int * a)
{
    int sum = 0;
    for(int i = 0;i <= n;i ++)
    {
        int thissum = 0;
        for(int j = i;j <= n;j ++)
        {
            thissum += a[j];
            if(thissum > sum)
            sum = thissum;
        }
    }
    return sum;
}
int main()
{
    int n;
    cin >> n;
    for(int i = 0;i < n;i ++)
    cin >> a[i];
    cout << MaxSum(n,a) << endl;
    return 0;
}

改进后的算法显然只需要O(n^2)的计算时间。上述改进是在算法设计技巧上的一个改进,能充分利用已经得到的结果,避免重复计算,节省了计算时间。

2.最大子段和问题的分治算法

      针对最大子段和这个具体问题本身的结构,还可以从算法设计的策略上对上述O(n^2)计算时间算法加以更深刻的改进。从这个问题的解的结构可以看出,它适合于用分治法求解。

        如果将所给的序列a[1:n]分为长度相等的两段a[1:n/2]和a[n/2 + 1:n],分别求出这两段的最大子段和,则a[1:n]的最大子段和有三种情形;

    (1)a[1:n]的最大子段和与a[1:n/2]的最大子段和相同;

    (2)a[1:n]的最大子段和与a[n/2 + 1:n]的最大子段和相同;

    (3)a[1:n]的最大子段和为a[i]到a[j]的和,且1 <= i <= n/2, n/2 + 1 <= j <= n。

(1)和(2)这两种情形可递归求得。对于情形(3),容易看出,a[n/2]与a[n/2+1]在最优子序列中。因此,可以在a[1:n/2]中计算出s1 = 子段a[i]到a[n/2]的最大和,并在a[n/2+ 1:n]中计算出s2=子段a[n/2+1]到a[j]的最大和。则s1 + s2 即为出现情形(3)时的最优值。据此可设计出求最大子段和的分治算法如下:

#include<iostream>
#include<cmath>
#include<algorithm>
#include<cstring>
using namespace std;
#define Max 50005
typedef long long LL;
LL a[Max];
LL MaxSubSum(LL *a,int left,int right)
{
    LL sum = 0;
    if(left == right)
    sum = a[left] > 0 ? a[left] : 0;
    else
    {
        LL center = (left + right) / 2;
        LL leftsum = MaxSubSum(a,left,center);
        LL rightsum = MaxSubSum(a,center + 1,right);
        LL s1 = 0;
        LL lefts = 0;
        for(LL i = center;i >= left;i --)
        {
            lefts += a[i];
            if(lefts > s1)
            s1 = lefts;
        }
        LL s2 = 0;
        LL rights = 0;
        for(LL i = center + 1;i <= right;i ++)
        {
            rights += a[i];
            if(rights > s2)
            s2 = rights;
        }
        sum = s1 + s2;
        if(sum < leftsum)
        sum = leftsum;
        if(sum < rightsum)
        sum = rightsum;
    }
    return sum;
}
LL MaxSum(int n,LL * a)
{
    return MaxSubSum(a,1,n);
}
int main()
{
    int n;
    cin >> n;
    for(int i = 1;i <= n;i ++)
    cin >> a[i];
    cout << MaxSum(n,a) << endl;
    return 0;
}

该算法所需的计算时间T(n)=O(nlogn).

3.最大子段和问题的动态规划算法

        在对上述分治算法的分析中注意到,若记b[j]=a[1:j]的最大子段和,则所求的最大子段和为

a[1:n]的最大子段和=b[1 :n]的最大值

        由b[j]的定义易知,当b[j - 1]>0时b[j] = b[j - 1] + a[ j ],否则b[j] = a[j]。由此可得计算b[j]的动态规划递归式

                    b[ j ] = max{ b[ j - 1] + a[ j ],a[ j ]}, 1 <= j <= n

据此,可设计出求最大子段和的动态规划算法如下。

#include<iostream>
#include<cmath>
#include<algorithm>
#include<cstring>
using namespace std;
#define Max 50005
long long a[Max];
long long MaxSum(int n,long long * a)
{
    long long sum = 0,b = 0;
    for(int i = 1;i <= n;i ++)
    {
        if(b > 0)
        b += a[i];
        else
        b = a[i];
        if(b > sum)
        sum = b;
    }
    return sum;
}
int main()
{
    int n;
    cin >> n;
    for(int i = 1;i <= n;i ++)
    cin >> a[i];
    cout << MaxSum(n,a) << endl;
    return 0;
}

上述算法显然需要O(n)计算时间和O(n)空间。

例题:

1049 最大子段和

基准时间限制:1 秒 空间限制:131072 KB 分值: 0 难度:基础题
N个整数组成的序列a[1],a[2],a[3],…,a[n], 求该序列如a[i]+a[i+1]+…+a[j]的连续子段和的最大值。当所给的整数均为负数时和为0。
例如:-2,11,-4,13,-5,-2,和最大的子段为:11,-4,13。和为20。
Input
第1行:整数序列的长度N(2 <= N <= 50000)
第2 - N + 1行:N个整数(-10^9 <= A[i] <= 10^9)
Output
输出最大子段和。
Input示例
6
-2
11
-4
13
-5
-2
Output示例
20

猜你喜欢

转载自blog.csdn.net/qq_41929449/article/details/80294315