题目描述
输入一个长度为 的整数序列,从中找出一段长度不超过 的连续子序列,使得子序列中所有数的和最大。
注意: 子序列的长度至少是 。
输入格式
第一行输入两个整数 。
第二行输入 个数,代表长度为 的整数序列。
同一行数之间用空格隔开。
输出格式
输出一个整数,代表该序列的最大子序和。
数据范围
,
保证所有输入和最终结果都在 范围内。
输入样例:
6 4
1 -3 5 1 -2 3
输出样例:
7
题目分析
这是一道 单调数列优化DP 的题目。
首先要先将原数列转化为前缀和数列,这样对于每个子序列我们都可以以 的复杂度得到区间和。
在暴力做法中,需要以 的复杂度枚举子序列的左右端点,这是不可接受的。那么我们考虑只枚举右端点的情况来解决这个题。
定义 表示以下标 i 结尾,且连续子序列长度不超过 m 的最大区间和。用数学式来表示:
我们将此时的 提出来,得到:
即我们需要维护前 个 中的最小值,这里我们就可以采用单调队列优化的方式。即维护一个先入先出的队列,队首是在当前下标之前的 个 的最小值,在枚举当前坐标的过程中更新队头,具体过程见代码。
Accept代码
#include <bits/stdc++.h>
using namespace std;
const int N = 300010;
int s[N], q[N];
int hh, tt; // 这里 hh = tt = 0,即一开始我们将下标 0 纳入队列
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
int n, m;
cin >> n >> m;
for (int i = 1; i <= n; i ++) cin >> s[i], s[i] += s[i - 1];
long long res = -1e10;
for (int i = 1; i <= n; i ++)
{
if (q[hh] < i - m) hh ++;
res = max(res, 1ll * s[i] - s[q[hh]]);
while (hh <= tt && s[q[tt]] >= s[i]) tt --; // 单调队列的核心代码
q[++ tt] = i;
}
cout << res << "\n";
return 0;
}