让 A 成为我们的输入数组,索引从零开始。我们可以在不改变结果的情况下以 M 为模减少 A。
首先,让我们通过计算一个表示A的前缀和的数组P,以M为模,将问题简化为一个稍微简单的问题>:
A = 6 6 11 2 12 1
P = 6 12 10 12 11 12
现在让我们按降序处理解决方案子数组的可能左边界。这意味着我们将首先确定从索引 n - 1 开始的最优解,然后确定从索引 n - 2 开始的最优解,以此类推。
在我们的示例中,如果我们选择 i = 3 作为左边界,则可能的子数组和由后缀 P[3..n-1] 表示加上一个常数a = A[i] - P[i]:
a = A[3] - P[3] = 2 - 12 = 3 (mod 13)
P + a = * * * 2 1 2
全局最大值也将出现在某一点。由于我们可以从右到左插入后缀值,我们现在将问题简化为:
给定一组值S和整数x和M,求S + x的最大值模M
这个很简单:只需使用平衡二叉搜索树来管理S的元素。给定一个查询x,我们希望找到S中小于M - x的最大值(即没有溢出的情况添加 x 时发生)。如果没有这个值,就用S的最大值。两者都可以在 O(log |S|) 时间内完成。
此解决方案的总运行时间:O(n log n)
这里有一些计算最大和的 C++ 代码。它还需要一些小的调整才能返回最优子数组的边界:
#include <bits/stdc++.h>
using namespace std;
int max_mod_sum(const vector<int>& A, int M) {
vector<int> P(A.size());
for (int i = 0; i < A.size(); ++i)
P[i] = (A[i] + (i > 0 ? P[i-1] : 0)) % M;
set<int> S;
int res = 0;
for (int i = A.size() - 1; i >= 0; --i) {
S.insert(P[i]);
int a = (A[i] - P[i] + M) % M;
auto it = S.lower_bound(M - a);
if (it != begin(S))
res = max(res, *prev(it) + a);
res = max(res, (*prev(end(S)) + a) % M);
}
return res;
}
int main() {
// random testing to the rescue
for (int i = 0; i < 1000; ++i) {
int M = rand() % 1000 + 1, n = rand() % 1000 + 1;
vector<int> A(n);
for (int i = 0; i< n; ++i)
A[i] = rand() % M;
int should_be = 0;
for (int i = 0; i < n; ++i) {
int sum = 0;
for (int j = i; j < n; ++j) {
sum = (sum + A[j]) % M;
should_be = max(should_be, sum);
}
}
assert(should_be == max_mod_sum(A, M));
}
}