>

详解 最大子段和

题目名称:最大子段和

 

题目描述:给出一段序列,选出其中连续且非空的一段使得这段和最大。

输入格式:

第一行是一个正整数N,表示了序列的长度。

第2行包含N个绝对值不大于10000的整数A[i],描述了这段序列。

输出格式:

仅包括1个整数,为最大的子段和是多少。子段的最小长度为1。

 

枚举

 最蠢的办法,枚举左端点和右端点,再求和,用一个max储存历史的最大值,就可以了。时间复杂度O(n3)。

 1 #include <stdio.h>
 2 #include <math.h>
 3 #include <string.h>
 4 int _Max(int x,int y){return x>y?x:y;}
 5 int num[10001],sum=0;
 6 int max=0;
 7 int main(){
 8     int n;
 9     scanf("%d",&n);
10     int i,j;
11     for(i=1;i<=n;i++){
12         scanf("%d",&num[i]);
13     }
14     for(i=1;i<n;i++){
15         for(j=i+1;j<=n;j++){
16             sum=0; 
17             for(int k=i;k<=j;k++)
18                 sum+=num[k];
19             max=_Max(sum,max);
20         }
21     }
22     printf("%d\n",max);
23     return 0;
24 }
View Code

在枚举的基础上,我们可以发现:求一个和为最大的子序列,那么作为任意一个子序列,若它的左端点是第i个元素,右端点是第j个元素,则它其中的元素之和可以表示为第1~j个元素和第1~i个元素之差。

这就是一个预处理。可以在枚举的基础上减少一些时间,但并不是最优,也是O(n2)。

 

分治

用分治求最大子段和应首先将这整个问题划分成规模更小的子问题,可以取中点,将这一个序列划分成长度相等的两个序列。对于此时的最大子段和,会出现3种情况,即:

1.最大子段和在第一个序列中,即在我们所取的中点左边。

2.最大子段和在第二个序列中,即在我们所取的中点右边;

3.最大子段和在第一个序列与第二个序列之间,即我们取的这个点被包含在这个子段中。这时,我们就应该继续以这个子段为基础,继续划分,知道不能划分为止。  

(其实上面两种情况也是要划分的)(滑稽)

将3种情况的最大子段和合并,取三者之中的最大值就为问题的解。时间复杂度为O(nlogn)。

 1 #include <stdio.h>
 2 #include <math.h>
 3 #include <string.h>
 4 int n,a[20001]; 
 5 int Sum(int left, int right){
 6     int sum=0;
 7     if(left==right){ 
 8         if(a[left]>0)
 9             sum=a[left];  
10         else sum=0;  
11     }  
12     else{  
13         int mid=(left+right)/2;
14         int l_sum=Sum(left,mid);
15         int r_sum=Sum(mid+1,right);
16         int s1=0,s2=0;
17         int lefts=0,rights=0;  
18         for(int i=mid;i>=left;i--){
19             lefts+=a[i];  
20             if(lefts>s1)s1=lefts;
21         }
22         for(int j=mid+1;j<=right;j++){  
23             rights+=a[j];
24             if(rights>s2)s2=rights;  
25         }
26         sum=s1+s2;
27         if(sum<l_sum)sum=l_sum;
28         if(sum<r_sum)sum=r_sum;
29     }
30     return sum;
31 }
32 int main(){
33     scanf("%d",&n);
34     int i,j;
35     for(i=1;i<=n;i++)
36         scanf("%d",&a[i]);
37     printf("%d",Sum(1,n));
38     return 0;
39 }
View Code

 

贪心

和动态规划(乖乖的先看下面吧)的思路类似,博主看了很久,觉得好像没有区别,所以就不讲思路了。自己看代码思考吧。

 1 #include <stdio.h>
 2 #include <math.h>
 3 #include <string.h>
 4 int main(){
 5     int n,max=-2147483645,num,sum=0;
 6     scanf("%d",&n);
 7     for(int i=1;i<=n;i++){
 8         scanf("%d",&num);
 9         sum+=num;
10         if(sum>max)max=sum;
11         if(sum<0)sum=0;
12     }
13     printf("%d",max);
14     return 0;
15 }
View Code

 

动态规划

枚举以从1到i为右端点的子段,用数组number[i]来保存第i个元素的值,sum[i]来保存以第i个元素结尾的最大子段和。显然,我们要求的就是sum整个数组中最大的一个数了。递归的边界条件:若i=1,则sum[i]=1。因为只有它一个元素。若i>=1,最优的整个子段只有这个元素本身。因为sum[i]是用来保存以第i个元素为右端点的最大子段和,所以这时的最大子段和就是number[i]和number[i]+sum[i-1]的比较,比较取最大即可。为什么要加上sum[i-1]呢?因为相比于以第i-2个元素结尾的子段来说,以i-1个元素结尾的子段显然要更长,结果更大的可能性也更大。状态转移方程:sum[i]=max(sum[i-1]+number[i],numberp[i]);  (i>1)

 1 #include <stdio.h>
 2 #include <math.h>
 3 #include <string.h>
 4 int _Max(int x,int y){return x>y?x:y;}
 5 int number[200001],sum[200001];
 6 int main(){
 7     int i,j;
 8     int n,maxx=0;
 9     scanf("%d",&n);
10     for(i=1;i<=n;i++)
11         scanf("%d",&number[i]);
12     sum[1]=number[1];
13     maxx=sum[1];
14     for(i=2;i<=n;i++){
15         sum[i]=_Max(sum[i-1]+number[i],number[i]);
16         maxx=_Max(sum[i],maxx);
17     }
18     printf("%d",maxx);
19 }
View Code

 

其实这个题目还有很多方法,这里就不介绍其他的了。

 

posted @ 2017-12-25 13:20  Leo_wey  阅读(2864)  评论(0编辑  收藏  举报