loj 最大连续和
简化题意
给你 n 个数的序列,找出一段连续的长度不超过 m 的非空子序列,使得这个序列的和最大。
\(1 \leq n , m \leq 2e5\)
思路
有一个非常显然的 DP,\(F_i = \max \Big\{ \sum_{j = i - k + 1}^i A_j \big| k \in [1,m]\Big\}\)
然而这样做是 \(\operatorname{O}(nm)\) 的,并不优秀。
我们令 \(s_i = \sum_{j = 1}^i A_j\),则:
\[\begin{aligned}
F_i&=\max \Big\{ \sum_{j = i - k + 1}^i A_j \big| k \in [1,m] \Big\} \\
F_i&=\max \Big\{ s_i - s_{i - k} \big| k \in [1, m]\Big\}\\
F_i&=s_i - \min\Big\{ s_{i-k} \big| k\in[1,m]\Big\}
\end{aligned}
\]
考虑用队列来维护决策值 \(s_{i-k}\) ,每次只需要在队首删除 \(s_{i-k-1}\),在队尾加入 \(s_{i-1}\),但是取最小值的操作还是需要 \(\operatorname{O}(m)\) 实现。
考虑在添加 \(s_{i-1}\) 的时候,设现在队尾元素为 \(s_k\) ,由于 \(k < i-1\),所以 \(s_k\) 必然比 \(s_{i-1}\) 先出队。若此时 \(s_{i-1} \leq s_k\),则 \(s_k\) 这个决策就永远不会再以后用到,可以将 \(s_k\) 从队尾删除,此时的队尾就形成了一个类似栈的一个东西。
同理,若队尾的两个元素 \(s_i\) 和 \(s_j\) ,若 \(i < j\)且 \(s_i \geq s_j\),则我们可以删掉 \(s_i\),因为 \(s_i\) 永远都不会用到了,此时的队列中的元素构成了一个单调递增的序列,即 \(s_1 < s_2 < \dots < s_k\)。
维护的时候只需要这么做就行了。
- 若当前队首元素 \(s_x\),有 \(x < i - m\) , 则 \(s_x\) 出队,知道队首元素 \(x \geq i-m\) 为止。
- 若当前队尾元素 \(s_k \geq s_{i-1}\),则 \(s_k\) 出队,知道 \(s_k < s_{i-1}\) 为止。
- 在队尾插入 \(s_{i-1}\)。
- 取出队列中的最小值,更新一下答案。
代码
#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define ll long long
#define N 200010
#define M 1010
using namespace std;
const int mod = 1e9+7;
const int inf = 0x3f3f3f3f;
int n, m, head, tail;
int s[N], a[N], q[N], f[N], num[N];
int read() {
int s = 0, f = 0; char ch = getchar();
while (!isdigit(ch)) f |= (ch == '-'), ch = getchar();
while (isdigit(ch)) s = s * 10 + (ch ^ 48), ch = getchar();
return f ? -s : s;
}
int main() {
n = read(), m = read();
for (int i = 1; i <= n; i++) a[i] = read(), s[i] = s[i - 1] + a[i];
int ans = -inf;
tail = 1, head = 1, q[tail] = 0, num[tail] = 0;
for (int i = 1; i <= n; i++) {
while (num[head] < i - m && head <= tail) head++;
ans = max(ans, s[i] - q[head]);
while (q[tail] >= s[i] && head <= tail) tail--;
q[++tail] = s[i], num[tail] = i;
}
cout << ans;
}

浙公网安备 33010602011771号