【题解提供者】刘枭
解法一
思路
我们可以枚举所有的区间,比较他们的和大小。
代码部分
#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 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 <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) 的。