P1115 最大子段和
给出一个长度为 nn 的序列 aa,选出其中连续且非空的一段使得这段和最大。
7
2 -4 3 -1 2 -4 3
分治的解法。
首先,假定有区间[l..r][l..r],其中间位置为midmid,其最大子段为[i..j][i..j]。那么显然,ii和jj必定符合下列三种情况之一:
1.l \leq{i} \leq{j} \leq{mid} l≤i≤j≤mid
2.i \leq{mid} <j \leq{r} i≤mid<j≤r
3.mid < i \leq{j} \leq{r} mid<i≤j≤r
只需要分别求出三种情况下的值,取其最大的即可。
其中,很容易求出第二种情况,即求出区间[i..mid][i..mid]与区间[mid+1..j][mid+1..j],将其相加即可。复杂度O(n)O(n)
如何处理第一种和第三种情况呢?也不难发现,第一种情况,其实就是求区间[1..mid][1..mid]中的最大值,第三种情况就是求区间[mid+1..r][mid+1..r]中的最大值。那么,只需递归求出即可。
显然,该解法的复杂度为 O(nlogn)O(nlogn) 通过此题是没问题的。
附上代码
`#include<cstdio>
int n , arr[200200]; //arr存储该序列
const int minn = -19260817; // 定义最小值
inline int Max( int a , int b) { return a > b ? a : b ;} //自定义 Max 函数(好像比stl的快一点)
int rec( int l , int r ) { //分治函数
if ( l == r ) { // l=r时,直接返回该位置的值
return arr[l];
}
int mid = ( l + r ) >> 1;
int sum = 0 , ret1 = minn , ret2 = minn; //ret1为[l..mid]区间内包含mid的最大子段和,ret2为[mid+1..r]区间内包含(mid+1)的最大子段和
for( int i = mid ; i >= l ; i-- ) {
sum += arr[i];
ret1 = Max( ret1 , sum );
} //求出[i..mid]区间最大值
sum = 0;
for( int i = mid+1 ; i <= r ; i++ ) {
sum += arr[i];
ret2 = Max( ret2 , sum );
} //求出[mid+1..r]区间最大值
return Max( Max( rec( l , mid ) , rec( mid + 1 , r ) ) , ret1 + ret2 ); //返回可能一 可能二 可能三 中的最大值
}
int main() { // 以下主函数
scanf("%d", &n );
for( int i = 1 ; i <= n ; i++ ) {
scanf("%d" , &arr[i] );
}
printf("%d" , rec(1 , n) );
return 0;
}`
//最大子段和问题 p64
#include<stdio.h>
#include<iostream>
using namespace std;
int MaxSum(int a[],int left,int right);
int a[100];
int main(void)
{
int n,j;
scanf("%d",&n);
for(j=0;j<n;j++)
scanf("%d",&a[j]);
printf("%d",MaxSum(a,0,n-1));
return 0;
}
int MaxSum(int a[],int left,int right)
{
int sum=0,midSum=0,leftSum=0,rightSum=0;
int center,s1,s2,lefts,rights;
if(left==right)
sum=a[left];
else{
center=(left+right)/2;
leftSum=MaxSum(a,left,center); //情况1,最大字段和全部取左边元素
rightSum=MaxSum(a,center+1,right);//情况2,最大字段和全部取右边元素
s1=0;lefts=0; //情况3 最大子段和横跨中间
for(int i=center;i>=left;i--) //求出s1:从中间到左边的最大和
{
lefts+=a[i];
if(lefts>s1) s1=lefts;
}
s2=0;rights=0;
for(int j=center+1;j<=right;j++) //求出s2 从中间到右边的最大和
{
rights+=a[j];
if(rights>s2) s2=rights;
}
midSum=s1+s2; //横跨中间的最大字段和为s1+s2
if(midSum<leftSum) sum=leftSum;
else sum=midSum;
if(sum<rightSum) sum=rightSum; //取三者的较大者
}
return sum;
}