题目链接:环形最大子段和

给定一个长度为 n 的环形数组 a1,a2,,an,其中 a1an 首尾相接,an 相邻的下一个元素是 a1a1 相邻的上一个元素是 an。现在我们想在环形数组 a1,a2,,an, 取连续且非空的一段,那么这段的和最大是多少?

限制:

  • 1n2×105
  • 104ai104

算法分析

本题难度中等,考察枚举及前缀和的技巧。
和最大的子段在数组 a 中只有两种情况:

  1. 最大子段包含的元素不同时在数组 a 两端
  2. 最大子段同时包含数组 a 两端的元素,即 a1an

第一种情况,说明最大子段和就是数组 a 的最大子段和;第二种情况,最大子段和为 a 的所有元素总和 - a 的最小子段和
在以上两种情况下的结果的最大值即为最终答案。
也就是说,需要计算数组 a 的最大子段和与最小子段和,这里以最大子段和为例:

50分做法: 双重循环枚举终点 j(1jn) 和起点 i(1ij),然后再利用循环计算 ai+ai+1++aj,同时更新最大值。该算法的时间复杂度为 O(n3)

70分做法:50 分做法的基础上可以利用前缀和计算 ai+ai+1++aj。该算法的时间复杂度为 O(n2)

代码实现
#include <bits/stdc++.h>
#define rep(i, n) for (int i = 0; i < (n); ++i)
using namespace std;
inline void chmin(int& x, int y) { if (x > y) x = y; }
inline void chmax(int& x, int y) { if (x < y) x = y; }
int main() {
int n;
cin >> n;
vector<int> a(n);
rep(i, n) cin >> a[i];
vector<int> s(n+1);
rep(i, n) s[i+1] = s[i]+a[i];
int maxS = -2e9, minS = 2e9;
for (int i = 1; i <= n; ++i) {
for (int j = i; j <= n; ++j) {
int now = s[j]-s[i-1];
chmin(minS, now);
chmax(maxS, now);
}
}
int ans = max(maxS, s[n]-minS);
cout << ans << '\n';
return 0;
}

100分做法:70 分做法中,内层循环的作用为在 j 固定不变的情况下找 sjsi1 的最大值。max{sjsi1}=max{sjs0,sjs1,sjs2,,sjsj1} 等价于 sjmin(si1),可以预处理得到 min(si1) 就可以砍掉一重循环。假设 mins 表示前缀和数组 s 的前 i 个元素的最小值,maxs 表示前缀和数组 s 的前 i 个元素的最大值。
该算法的时间复杂度为 O(n)

但这样还是有问题:

hack数据:

in
3
-3 -2 -3
out
-2

我们还需特判一下这种 sn 等于 minS 的情况,答案应该为线形的最大子段和。

代码实现
#include <bits/stdc++.h>
#define rep(i, n) for (int i = 0; i < (n); ++i)
using namespace std;
inline void chmin(int& x, int y) { if (x > y) x = y; }
inline void chmax(int& x, int y) { if (x < y) x = y; }
int main() {
int n;
cin >> n;
vector<int> a(n);
rep(i, n) cin >> a[i];
vector<int> s(n+1);
rep(i, n) s[i+1] = s[i]+a[i];
vector<int> mins(n+1), maxs(n+1);
for (int i = 1; i <= n; ++i) {
mins[i] = min(mins[i-1], s[i]);
maxs[i] = max(maxs[i-1], s[i]);
}
int maxS = -2e9, minS = 2e9;
for (int i = 1; i <= n; ++i) {
chmin(minS, s[i]-maxs[i-1]);
chmax(maxS, s[i]-mins[i-1]);
}
int ans = max(maxS, s[n]-minS);
if (s[n]-minS == 0) ans = maxS;
cout << ans << '\n';
return 0;
}