最大子段和以及拓展
title: 最大子段和以及拓展
categories:
- ICPC
tags:
- null
abbrlink: d90d19c2
date: 2024-03-07 00:00:00
无限制最长连续的子序列和
动态规划转移方程
dp[i] = max(dp[i-1] + a[i], a[i]);
最终结果在 dp
数组中线性扫描找出最大值:
int pos = max_element(dp + 1, dp + 1 + n) - dp;
cout << dp[pos] << " ";
dp[i]
表示以序列中第 i
个数字结尾的最大子段和。
转移方程分析:
- 如果选择第
i-1
个数,则由dp[i-1]
转移而来。 - 如果不选择第
i-1
个数,则只能自己作为一个子段存在,因为必须以第i
个数结尾。
优化:记录子段端点
在题目中,还需要给出具体是哪段子段和最大(即左端点和右端点)。
初始方法:先求出最大值,再排序所有符合最大值的子段,按题目要求取左端和右端尽可能小的子段。
优化方法:在线性扫描时维护最大值,同时更新左右端点。
有长度限制最长连续的子序列和
类型:单调队列优化 DP
题目描述
给定一个长度为 n
的序列 a
,找出其中元素总和最大且长度不超过 m
的连续子区间。
闫氏DP分析法
- 状态表示:
f_i
表示以i
为右端点,长度不超过m
的连续子区间。 - 属性:
f_i
表示区间的总和最大(Max
)。 - 状态转移方程:
$$
f_i = \max{s_i - s_j} \quad (1 \le i - j \le m)
$$
其中,s_i
为前缀和,j
的范围为i - m <= j <= i - 1
。
进一步优化:
$$
f_i = s_i - \min{s_j} \quad (1 \le i - j \le m)
$$
单调队列优化
从前向后维护一个长度不超过 m
的区间的最小值,使用单调队列优化。
时间复杂度:O(n)
(每个元素至多入队出队一次,每次转移为 O(1)
)。
代码实现
using namespace std;
typedef long long LL;
const int N = 3e5 + 10;
int n, m;
LL s[N], que[N];
int main() {
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; i++) scanf("%lld", &s[i]), s[i] += s[i - 1]; // 处理前缀和
LL res = -1e18;
int hh = 0, tt = 0; que[0] = 0;
for (int i = 1; i <= n; i++) {
while (hh <= tt && i - que[hh] > m) hh++; // 维护窗口大小
res = max(res, s[i] - s[que[hh]]); // 更新最大值
while (hh <= tt && s[que[tt]] >= s[i]) tt--; // 维护单调队列
que[++tt] = i;
}
printf("%lld\n", res);
return 0;
}
参考链接:
单调队列优化DP题解
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来源 爱飞鱼的blog!