我们可以枚举所有的区间,比较他们的和大小。
#include
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 n−len+1,而包括这个点往左的所有点都可以作为长度为 l e n len len 的区间的左端点,所以 n u m ( l e n ) = n − l e n + 1 num(len) = n - len + 1 num(len)=n−len+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+(n−1)×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)+(n−1)×(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}; B−A=n2+(n−1)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=B−6n×(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)2−6n×(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; 23−13=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; 33−23=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; 43−33=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)3−n3=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)3−13=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)3−13−3×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,j−1] 的和加上 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
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) 的。