最大子段和(3e3)题解

【题解提供者】刘枭

解法一

思路

我们可以枚举所有的区间,比较他们的和大小。

代码部分

#include <iostream>
using namespace std;
#define ll long long 

int n,t,a[3001],ans=-2e9;

int main()
{ 
  cin>>n;
  for(int i=1;i<=n;i++)cin>>a[i];
  for(int i=1;i<=n;i++){//枚举区间的左端点
    for(int j=i;j<=n;j++){//枚举区间的右端点
      int tem=0;
      for(int k=i;k<=j;k++){
      tem+=a[k];
      }
      ans=max(ans,tem);//更新答案
    }
  }
  cout<<ans<<'\n';
  return 0;
}

算法分析

这是最朴素的暴力算法,总共有三重循环,前两重循环枚举了所有区间,第三重循环对区间进行求和。我们来分析这个算法的时间复杂度,设区间长度为 l e n len len,长度为 l e n len len 的区间有 n u m ( l e n ) num(len) num(len) 个,那么序列中最靠右的长度为 l e n len len 的区间的左端点位于 n − l e n + 1 n-len+1 nlen+1,而包括这个点往左的所有点都可以作为长度为 l e n len len 的区间的左端点,所以 n u m ( l e n ) = n − l e n + 1 num(len) = n - len + 1 num(len)=nlen+1

因此整个算法的运行次数为:
A . ∑ l e n = 1 n ( n u m ( l e n ) × l e n ) = n × 1 + ( n − 1 ) × 2 + . . . + 1 × n ; A.\sum_{len = 1}^n(num(len)\times len)=n\times 1+(n-1)\times 2+...+1\times n; A.len=1n(num(len)×len)=n×1+(n1)×2+...+1×n;

现在我们构造一个新的式子:
B . ( n + 1 ) × ∑ i = 1 n i = n × ( n + 1 ) + ( n − 1 ) × ( n + 1 ) + . . . + 1 × ( n + 1 ) ; B.(n+1)\times \sum_{i=1}^ni=n\times (n+1)+(n-1)\times (n+1)+...+1\times(n+1); B.(n+1)×i=1ni=n×(n+1)+(n1)×(n+1)+...+1×(n+1);

B B B 式减去 A A A 式可得:
B − A = n 2 + ( n − 1 ) 2 + . . . + 1 2 = n × ( n + 1 ) × ( 2 n + 1 ) 6 ; B- A = n^2+(n-1)^2+...+1^2 = \frac{n\times(n+1)\times(2n+1)}{6}; BA=n2+(n1)2+...+12=6n×(n+1)×(2n+1);
(上面用了平方和公式,对于该公式的推导将会在本分析的末尾展示)

所以有:
A = B − n × ( n + 1 ) × ( 2 n + 1 ) 6 ; A = B - \frac{n\times(n+1)\times(2n+1)}{6}; A=B6n×(n+1)×(2n+1);

B B B 式使用等差数列求和公式化简:
A = n × ( n + 1 ) 2 2 − n × ( n + 1 ) × ( 2 n + 1 ) 6 = n × ( n + 1 ) × ( n + 2 ) 6 ; A = \frac{n\times(n+1)^2}{2} - \frac{n\times(n+1)\times(2n+1)}{6} = \frac{n\times(n+1)\times(n+2)}{6}; A=2n×(n+1)26n×(n+1)×(2n+1)=6n×(n+1)×(n+2);

根据 bigO 表示法,这个算法的时间复杂度是 O ( n 3 ) O(n^3) O(n3) 级别的。所以在本题的极限数据下,其所需的计算次数达到了惊人的 2.7 × 1 0 10 2.7\times10^{10} 2.7×1010,而程序在 OJ 中每秒大概只能进行 2 × 1 0 8 2\times10^8 2×108 次基础运算,所以上述算法在本题会超时。(优化见解法二

平方和公式推导

我们构造一系列式子:
2 3 − 1 3 = 3 × 1 2 + 3 × 1 + 1 ; 2^3 - 1^3 =3\times1^2+3\times 1+1; 2313=3×12+3×1+1;
3 3 − 2 3 = 3 × 2 2 + 3 × 2 + 1 ; 3^3 - 2^3 =3\times2^2+3\times 2+1; 3323=3×22+3×2+1;
4 3 − 3 3 = 3 × 3 2 + 3 × 3 + 1 ; 4^3 - 3^3 =3\times3^2+3\times 3+1; 4333=3×32+3×3+1;
. . . ... ...
( n + 1 ) 3 − n 3 = 3 × n 2 + 3 × n + 1 ; (n+1)^3 - n^3 = 3\times n^2 + 3\times n+1; (n+1)3n3=3×n2+3×n+1;

将上述式子累加可得:
( n + 1 ) 3 − 1 3 = 3 × ∑ i = 1 n i 2 + 3 × n × ( 1 + n ) 2 + 1 ; (n+1)^3 - 1^3 = 3\times \sum_{i=1}^ni^2+3\times\frac{n\times(1+n)}{2}+1; (n+1)313=3×i=1ni2+3×2n×(1+n)+1;

移项得:
∑ i = 1 n i 2 = 1 3 ( ( n + 1 ) 3 − 1 3 − 3 × n × ( n + 1 ) 2 − n ) = n × ( n + 1 ) ( 2 n + 1 ) 6 ; \sum_{i=1}^ni^2=\frac{1}{3}((n+1)^3-1^3-3\times\frac{n\times(n+1)}{2}-n)=\frac{n\times(n+1)(2n+1)}{6}; i=1ni2=31((n+1)3133×2n×(n+1)n)=6n×(n+1)(2n+1);

推导完成。

解法二

思路

解法一中对于区间的求和每次都遍历了整个区间,而一些区间之间有重叠的子区间,显然我们对某些区域进行了很多重复计数。具体的我们来看第二重循环枚举的区间 :
[ i , i ] , [ i , i + 1 ] , [ i , i + 2 ] , . . . , [ i , n ] ; [i,i], [i, i + 1],[i,i+2],...,[i,n]; [i,i],[i,i+1],[i,i+2],...,[i,n];

不难发现对于以 i i i 为左端点的区间的枚举是连续的,除了 [ i , i ] [i,i] [i,i],每个区间 [ i , j ] [i,j] [i,j] 的和都是由上一个枚举的区间 [ i , j − 1 ] [i,j-1] [i,j1] 的和加上 a j a_j aj 得到的。所以我们无需对所有区间都遍历求和,只要用一个临时变量 t e m tem tem ,每次枚举右端点 j j j 时就加上 a j a_j aj 就得到了区间 [ i , j ] [i,j] [i,j] 的和。

代码部分

#include <iostream>
using namespace std;
#define ll long long 

int n,t,a[3001],ans=-2e9;

int main()
{ 
  cin>>n;
  for(int i=1;i<=n;i++)cin>>a[i];
  for(int i=1;i<=n;i++){//枚举区间的左端点
    int tem=0;
    for(int j=i;j<=n;j++){//枚举区间的右端点
      tem+=a[j];
      ans=max(ans,tem);//更新答案
    }
  }
  cout<<ans<<'\n';
  return 0;
}

算法分析

我们优化了单个区间求和的复杂度,每个区间的求和花费都是 O ( 1 ) O(1) O(1) 的,所以这个解法的复杂度等于区间个数:
∑ l e n = 1 n n u m ( l e n ) = n + ( n + 1 ) + . . . + 1 = n × ( n + 1 ) 2 ; \sum_{len=1}^nnum(len)=n+(n+1)+...+1= \frac{n \times (n+1)}{2}; len=1nnum(len)=n+(n+1)+...+1=2n×(n+1);

因此,这个算法的复杂度是 O ( n 2 ) O(n^2) O(n2) 的。

  • 28
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值