題目名稱:最大子段和
題目描述:給出一段序列,選出其中連續且非空的一段使得這段和最大。
輸入格式:
第一行是一個正整數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 }
在枚舉的基礎上,我們可以發現:求一個和為最大的子序列,那么作為任意一個子序列,若它的左端點是第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 }
貪心
和動態規划(乖乖的先看下面吧)的思路類似,博主看了很久,覺得好像沒有區別,所以就不講思路了。自己看代碼思考吧。
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 }
動態規划
枚舉以從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 }
其實這個題目還有很多方法,這里就不介紹其他的了。
