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

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

限制:

  • \(1 \leqslant n \leqslant 2 \times 10^5\)
  • \(-10^4 \leqslant a_i \leqslant 10^4\)

算法分析

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

  1. 最大子段包含的元素不同时在数组 \(a\) 两端
  2. 最大子段同时包含数组 \(a\) 两端的元素,即 \(a_1\)\(a_n\)

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

50分做法: 双重循环枚举终点 \(j (1 \leqslant j \leqslant n)\) 和起点 \(i(1 \leqslant i \leqslant j)\),然后再利用循环计算 \(a_i+a_{i+1}+\cdots + a_j\),同时更新最大值。该算法的时间复杂度为 \(O(n^3)\)

70分做法:\(50\) 分做法的基础上可以利用前缀和计算 \(a_i + a_{i+1} + \cdots + a_j\)。该算法的时间复杂度为 \(O(n^2)\)

代码实现
#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\) 固定不变的情况下找 \(s_j - s_{i-1}\) 的最大值。\(\max\{s_j - s_{i-1}\} = \max\{s_j-s_0, s_j-s_1, s_j-s_2, \cdots, s_j-s_{j-1}\}\) 等价于 \(s_j - \min(s_{i-1})\),可以预处理得到 \(\min(s_{i-1})\) 就可以砍掉一重循环。假设 mins 表示前缀和数组 \(s\) 的前 \(i\) 个元素的最小值,maxs 表示前缀和数组 \(s\) 的前 \(i\) 个元素的最大值。
该算法的时间复杂度为 \(O(n)\)

但这样还是有问题:

hack数据:

in
3
-3 -2 -3

out
-2

我们还需特判一下这种 \(s_n\) 等于 \(\operatorname{min}S\) 的情况,答案应该为线形的最大子段和。

代码实现
#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;
}