1 条题解
-
0
思路与算法
设S[i]为前i个数的和,连续段(j+1)..i的和为S[i]-S[j]。长度在1..m内,等价于j处于[max(0,i-m),i-1],因此每个右端点只需减去这一窗口中的最小前缀和。
维护前缀下标的单调队列,队内对应S值严格递增。处理i时,先删掉下标小于i-m的过期候选,再用队首更新最大和;随后从队尾删掉S值大于等于S[i]的旧候选,最后插入i。初始加入前缀下标0。
必须先查询再插入i,保证选取的是非空区间;答案初始化为足够小的负数,才能处理全负序列。
正确性说明
任意以i结尾的合法非空区间,都与上述窗口中一个前缀下标j一一对应。选取最小S[j]便使区间和最大。
较早的候选a若满足S[a]>=S[b]且a<b,则对未来右端点,b给出的区间和不小于a,且b不会比a更早过期,所以a可以永久删除。过期删除保证候选仍在长度限制内,队列单调性保证队首是合法窗口的最小值。枚举全部右端点,因此得到全局最大非空限长连续和。
复杂度
前缀数组与队列初始化O(n)。每个下标入队一次、至多出队一次,总时间O(n),空间O(n)。m大于n时算法仍然有效,无需额外扩大存储。
易错点
过期条件是下标<i-m,左边界i-m仍应保留。插入当前前缀之前就要查询,否则空段会让全负序列错误输出0。m可以大于n;m=1时答案是最大单个元素。保留相等前缀中较新的下标,可使候选更晚过期。
参考代码(C++11)
#include <bits/stdc++.h> using namespace std; using ll = long long; int main() { ios::sync_with_stdio(false); cin.tie(nullptr); int n, m; cin >> n >> m; vector<ll> prefix(n + 1); vector<int> q(n + 1); int head = 0, tail = 1; q[0] = 0; ll answer = LLONG_MIN; for (int i = 1; i <= n; ++i) { ll x; cin >> x; prefix[i] = prefix[i - 1] + x; while (head < tail && q[head] < i - m) ++head; answer = max(answer, prefix[i] - prefix[q[head]]); while (head < tail && prefix[q[tail - 1]] >= prefix[i]) --tail; q[tail++] = i; } cout << answer << '\n'; }
- 1