最大子序和
最大子序和问题的单调队列解法:结合前缀和与单调队列优化。
一句话算法
长度受限的最大子段和,就是固定右端点时,在合法左端点里找最小前缀和。
问题模型
给定序列
设前缀和为:
区间
当右端点为
所以要最大化
核心直觉
右端点
[i-m, i-1]
我们维护这个窗口里的最小前缀和下标,队头就是当前最优左端点。
算法步骤
-
预处理前缀和
S[0..n],其中S[0]=0。 -
用单调队列维护候选前缀下标
j。 -
初始把
0入队,表示子段可以从第1个元素开始。 -
从左到右枚举右端点
i。 -
删除所有不满足
j >= i-m的过期队头。 -
用队头更新答案:
-
将当前前缀下标
i加入队列前,弹出所有S[q.back()] >= S_i的下标,保持前缀和递增。 -
把
i加入队尾,继续处理下一个右端点。
算法证明
核心不变量: 处理右端点
- 合法性。 子段长度不超过
,所以左端点前缀下标必须满足 。每次先删除过期队头,队列中的下标都合法。 - 最优性。 对固定右端点
, 已确定。要最大化 ,只需要让 最小。 - 单调性。 若
j<i且S[j] >= S[i],那么j更早过期,前缀和还不更小,以后不可能比i更优,可以删除。 - 队头正确。 队列按前缀和值递增,队头就是当前合法范围内最小的前缀和下标。
因此每个右端点的最优子段都会被正确统计,所有右端点取最大值就是答案。
复杂度分析
每个前缀下标最多入队一次、出队一次。
- 时间复杂度:
。 - 空间复杂度:
,若只看队列规模为 。
代码实现
cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#include <bits/stdc++.h>
using namespace std;
int main() {
int n, m;
cin >> n >> m;
vector<long long> prefix(n + 1, 0);
for (int i = 1; i <= n; i++) {
long long x;
cin >> x;
prefix[i] = prefix[i - 1] + x;
}
long long ans = LLONG_MIN;
deque<int> q;
q.push_back(0);
for (int i = 1; i <= n; i++) {
while (!q.empty() && q.front() < i - m) {
q.pop_front();
}
ans = max(ans, prefix[i] - prefix[q.front()]);
while (!q.empty() && prefix[q.back()] >= prefix[i]) {
q.pop_back();
}
q.push_back(i);
}
cout << ans << "\n";
return 0;
}
测试用例
输入:
6 3
1 -2 3 5 -1 2
输出:
8
解释:长度不超过 3 的最大子段是 3 5,和为 8。
经典例题
1. AcWing 135
最大子序和模板题。重点是把“长度不超过
2. luogu-P1714
同一模型。枚举右端点,单调队列维护合法范围内的最小前缀和。
应用分类详解
长度受限最大子序和的本质是:固定右端点后,在一个滑动范围内找最小前缀和。
一、长度受限区间和
- 典型模式: 连续子段长度不能超过或必须落在某个范围内,要求最大和或最小和。
- 识别信号: 出现“长度不超过
”“连续子段”“最大区间和”。 - 核心建模: 区间和写成
,右端点固定时维护合法 的最小前缀和。
| 应用场景 | 经典题目 | 核心思路 |
|---|---|---|
| 最大子序和 | AcWing 135 | 队头是当前合法范围最小前缀和 |
| 长度限制最大连续和 | luogu-P1714 | 枚举右端点,维护左端点窗口 |
二、前缀和差值优化
- 典型模式: 目标可以写成当前值减去历史最优值。
- 识别信号: 式子形如
current - min(previous),且previous的范围随当前位置单调移动。 - 核心建模: 用单调队列维护历史候选中的最小值或最大值。
| 应用场景 | 经典题目 | 核心思路 |
|---|---|---|
| 区间收益最大化 | 常见收益题 | 当前收益减去窗口内最低成本 |
| 线性 DP 优化 | 决策窗口右移模型 | 队头保存当前最优前驱 |