题解:最大子序和 (限定长度)
1. 题目简述
给定一个长度为
形式化: 在数列
数据范围:
2. 思路分析
朴素前缀和( - 超时)
首先,对于区间求和问题,我们第一时间想到前缀和。
设
题目要求找出
(区间长度限制) - 使得
最大。
对于固定的
如果暴力枚举
单调队列优化( - 正解)
我们发现,当
我们需要在窗口
具体步骤:
- 预处理:计算前缀和数组
。 - 初始化:单调队列中先放入下标
(因为 是一个合法的减数,对应从第1个数开始选的情况)。 - 遍历:
从 到 : - 去尾(维护单调性):如果当前
比队尾元素对应的 值还小,说明当前的 更优且更靠后,队尾元素淘汰。弹出队尾,直到满足单调性或队列空。 - 入队:将当前下标
入队(注意:这里 入队是作为未来 时的减数 存在的)。 - 删头(维护时效性):检查队头元素
。我们当前计算答案时,右端点是 ,那么合法的左端点 必须满足 。如果 ,说明这个 离得太远了,超出了长度 的限制,弹出队头。 - 更新答案:此时队头元素就是当前窗口内最小的
。最大子段和 。
- 去尾(维护单调性):如果当前
注意点:
这里操作顺序略有讲究。对于当前的
3. 样例推演
输入:n=5, m=2,数组 1 2 3 4 5
前缀和 0, 1, 3, 6, 10, 15
| i | 当前 S[i] | 队列状态 (存下标) | 队头是否过期? ( |
计算答案 |
维护单调性并入队 |
|---|---|---|---|---|---|
| init | {0} |
- | - | - | |
| 1 | {0} |
否 ( |
{0, 1} |
||
| 2 | {0, 1} |
否 ( |
{0, 1, 2} |
||
| 3 | {0, 1, 2} |
是 ({1, 2} |
{1, 2, 3} |
||
| 4 | {1, 2, 3} |
是 ({2, 3} |
入队 {2, 3, 4} |
||
| 5 | {2, 3, 4} |
是 ({3, 4} |
入队 {3, 4, 5} |
最终答案:9。
4. AC 代码
cpp
#include <iostream>
#include <vector>
#include <deque>
#include <algorithm>
using namespace std;
// 即使题目说由int范围,但涉及累加,开long long是个好习惯
using ll = long long;
const int MAXN = 500005;
ll s[MAXN]; // 前缀和数组
int main() {
// I/O 加速
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int n, m;
if (!(cin >> n >> m)) return 0;
s[0] = 0;
for (int i = 1; i <= n; ++i) {
int x;
cin >> x;
s[i] = s[i - 1] + x;
}
// 单调队列存放下标,维护的是 S[j] 的最小值(单调递增队列)
deque<int> q;
q.push_back(0); // 初始化,放入S[0]
// 初始化答案为一个极小值
ll max_ans = -1e18;
for (int i = 1; i <= n; ++i) {
// 1. 移除过期元素 (队头下标 j 必须满足 i - j <= m,即 j >= i - m)
// 如果 q.front() < i - m,说明该左端点距离 i 超过了 m,不合法
while (!q.empty() && q.front() < i - m) {
q.pop_front();
}
// 2. 更新答案
// 当前合法的最小前缀和就是 s[q.front()]
// 以 i 为结尾的最大子段和 = s[i] - min(s[j])
if (!q.empty()) {
max_ans = max(max_ans, s[i] - s[q.front()]);
}
// 3. 维护单调性并入队 (准备作为未来的左端点 j)
// 我们需要队列保持单调递增 (因为我们要找最小的 S[j] 来减)
// 如果当前的 s[i] 比队尾的 s[back] 还小,那队尾就没用了
while (!q.empty() && s[q.back()] >= s[i]) {
q.pop_back();
}
q.push_back(i);
}
cout << max_ans << endl;
return 0;
}
5. 总结
- 转化问题:将“子段和最大”转化为“前缀和差值最大” (
)。 - 固定一端:枚举右端点
,问题变为在合法范围内找最小的 。 - 单调队列:维护一个值单调递增的队列,存储下标。
- 取值:取队头 (最小的减数)。
- 入队:保持单调递增 (若当前值更小,则弹出队尾)。
- 出队:判断下标距离是否超过
。