AcWing
  • 首页
  • 课程
  • 题库
  • 更多
    • 竞赛
    • 题解
    • 分享
    • 问答
    • 应用
    • 校园
  • 关闭
    历史记录
    清除记录
    猜你想搜
    AcWing热点
  • App
  • 登录/注册

AcWing 135. 最大子序和【单调队列优化DP】    原题链接    简单

作者: 作者的头像   一只野生彩色铅笔 ,  2021-09-18 22:57:37 ,  所有人可见 ,  阅读 10229


95


14

最近在补全提高课所有题目的题解,宣传一下汇总的地方提高课题解补全计划

题目描述

给定一个长度为 $n$ 的序列 $a$,找出其中 元素总和最大 且 长度 不超过 $m$ 的 连续子区间

分析

看到求一段 连续区间 的和的问题,会想到用 前缀和 进行优化,然后就是 枚举 区间的问题

暴力枚举 子区间是一种方法,但没有优化空间;因此不妨 枚举 子区间的 右端点

闫氏DP分析法

状态表示-集合 $f_i$:以 $i$ 为 右端点,长度 不超过 $m$ 连续子区间

状态表示-属性 $f_i$:区间的总和最大 $Max$

状态计算 $f_i$:

$$ f_i = \max\big\{{ s_i - s_j }\big\} \qquad (1 \le i - j \le m) $$

观察这个转移方程,首先这里的 $j$ 是有范围的:$i - m \le j \le i - 1 $

其次,$s_i$ 作为一个常量,可以提到外面去:$f_i = s_i - \min\big\{{ s_j }\big\} \qquad(1 \le i - j \le m)$

从前向后 维护一个长度不超过 $m$ 的区间的 最小值,就想到我们最熟悉的 滑动窗口模型 了

那么该状态 转移方程 就可以用 单调队列 进行优化了(你要是想线段树,树状数组搞搞也不是不行)

如何在 队头 维护一个 最小值 的 单增队列,请移步 算法基础课 学习,本题解不会讲解

Code

时间复杂度: $O(n)$ (每个元素至多入队出队一次,每次转移又是 $O(1)$ 的)

为了避免这个 高亮BUG,单调队列 q[] 改名为 que[] 了

由于这里 f[i] 没有相互依赖的关系,最后答案是统计所有 f[i] 的最大值

故我们直接用一个变量 res 来计算

#include <iostream>
#include <cstring>
#include <algorithm>

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;
}

26 评论


用户头像
krio   2023-02-23 21:37      4    踩      回复

i - que[hh] > m这里怎么理解?
我想的是i-que[hh]+1>=m,`i-que[hh]+1表示长度,感觉很合理啊…

用户头像
羽肿   2023-02-24 21:57      7    踩      回复

获得[a,b]的写法是s[b]-s[a-1]而非s[a]

用户头像
sTao   2023-02-26 01:28      7    踩      回复

因为这里窗口存储的是前缀和数组的下标,而计算区间和时将两个前缀和s[i],s[j]相减后,实际求得的是原数组中区间长度为 i-j的子数组,而不是i-j+1

用户头像
__007   2023-03-04 18:33      9    踩      回复

/*
举个栗子:i=10,m=3 则应该是
(1)m=3 ->8 9 10 a[8]+a[9]+a[10]=s[10]-s[7]
(2)m=2 ->9 10 a[9]+a[10]=s[10]-s[8]
(3)m=1 ->10 a[10]=s[10]-s[9]
由于当前遍历到的是i=10,所以s[10]是固定值,变化的是后面的s[j], i-m<=j<=i-1
我们在计算以10为终点的区间最大和时,队列中存入的应该是 7,8,9的索引号
想要求最大值,就转化为求s[j]的最小值
状态表示: s[i]-min(s[i-1],s[i-2],…,s[i-m])
其中最小值的下标记录在维护好的单调队列队头中!

 这里需要考虑一下边界情况,比如i=1,也就是刚刚开始枚举第1个数,此时如果队列是空的,那么第1个数将无法
 取得队列头,因为队列是空的嘛!此时程序就会有问题,需要特判!这样太复杂了,一般的通用作法是:事先安排进去
 一个哨兵,它的作用就是解决边界情况,解决特判问题。

 哨兵的值视情况而定,比如本题,就是想解决第1个数字查询它前面的最小前缀和对应的索引号,也就是s[0]=0,
 我们直接把哨兵=0手动入入队列即可。
 */
用户头像
__007   2023-03-04 18:33      1    踩      回复

https://www.cnblogs.com/littlehb/p/15801560.html

用户头像
lll_34   2023-07-16 22:07      2    踩      回复

因为:que[j]的范围在[i-m,i-1]上,i-que[j]的范围应该在[1,m]上面,当i-que[j]>m时,说明que[j]这个值应该淘汰。


用户头像
乔巴船长   2023-03-07 16:04      1    踩      回复

为什么先插入s[i],再取res=max不行

用户头像
做梦都ac   2023-03-21 23:17      3    踩      回复

因为把s[i]加到队列里后可能会把之前的最小值弹出,自己变成最小值了,会丢掉之前m长度的最小值记录

用户头像
acwing_254457   2023-06-07 14:39         踩      回复

下标范围不包括i本身


用户头像
大四萌新.   2022-06-01 19:23      1    踩      回复

tt 为啥不是 -1 了?

用户头像
绿水   2022-06-01 20:20      7    踩      回复

因为插入了一个前缀和标杆0,不加0的话,如果第一个元素能取到的情况下,每次都取不到,模拟下就能明白,因为减的是s[l - 1]

用户头像
祁连飞鱼   2022-09-01 17:01      3    踩      回复

因为求的是i之前,不包括i的长度为m的滑动窗口的最小值,在前m个元素为正值时,s[i]递增,这时窗口内最小值为s[0],表示取1~i个元素求和,所以s[0]应当为第一个元素。这也是为什么先判断队头是否出队,接着取max再将i元素入队的原因(正常是,先将i入队,再判断队头是否出队)。

用户头像
yindujuxi   2023-01-18 16:49    回复了 祁连飞鱼 的评论         踩      回复

正常不也因该先i是否能成为最大值或最小值将t–;再进行i入队吗

用户头像
yindujuxi   2023-01-18 16:50    回复了 yindujuxi 的评论         踩      回复

如有不对还请指出

用户头像
祁连飞鱼   2023-01-18 19:00    回复了 yindujuxi 的评论      1    踩      回复

哈哈,是我写错了,谢谢纠正


用户头像
YAX_AC   2024-03-31 21:41         踩      回复

为什么是s[i] - s[q[hh]]呢,前缀和求[q[hh] ~ i]求这个区间里的和,不应该是s[i] - s[q[hh]-1]吗,求大佬解惑

用户头像
神崎日照   2024-10-10 20:10         踩      回复

维护的不是区间的左端点,是区间左端点减1到i-1,是维护的j-1那个数组


用户头像
马南锡   2024-03-24 15:25         踩      回复

妙


用户头像
学不会动态规划不改名   2023-01-29 22:50         踩      回复

#include[HTML_REMOVED]
#include[HTML_REMOVED]
using namespace std;
int n, m;
int const N=3e5 + 10;
long long sum[N],a[N],dp[N];
long long res= INT_MIN;

int main() {
cin >> n >> m;
for (int i = 1; i <= n; i) {
cin >> a[i];
sum[i] = sum[i - 1] + a[i];
dp[i]= INT_MIN;
}
for (int i = m; i <= n; i
){
for (int j = i - m; j <= i - 1; j++) {
dp[i] = max(dp[i], sum[i] - sum[j]);
}

    res = max(res, dp[i]);
}
cout << res;
return 0;

}分享一下暴力解法虽然超时了但是可以过11/14的数据,思路挺简单的

用户头像
橦橦   2023-02-23 16:21         踩      回复

双重循环的遍历是有点问题,前m个数的前缀和是遍历不到的,比如m=4,那sum[3],sum[2],sum[1]都遍历不到,如果前三个数恰好是正数,其他数都是负数,那答案本身就出问题了

用户头像
学不会动态规划不改名   2023-02-25 15:36    回复了 橦橦 的评论         踩      回复

感谢提的意见,确实是这样的,看来是我误打误撞混分混到了


用户头像
Bianca   2023-01-16 16:17         踩      回复

s[que[tt]] >= s[i],为什么是前缀和的s 来比较

用户头像
yindujuxi   2023-01-18 16:46         踩      回复

我们维护的的单调队列里面相当于s[l-1]的值,想要求得和最大,就要使队头元素是最小这样才s[r]-s[l-1]得到最大值,如果区间里面的元素大于队头那么直接删除大于他的,如果里面元素比他小他可能在前面元素出队后成为最小就不用删除他前面的数


用户头像
lyxleo   2022-11-09 10:35         踩      回复

搞了半天贪心,原来是个dp


用户头像
gtt   2022-04-16 20:00         踩      回复

Orz


用户头像
我没素质   2022-03-21 22:39         踩      回复

厉害!!


App 内打开
你确定删除吗?
1024
x

© 2018-2025 AcWing 版权所有  |  京ICP备2021015969号-2
用户协议  |  隐私政策  |  常见问题  |  联系我们
AcWing
请输入登录信息
更多登录方式: 微信图标 qq图标 qq图标
请输入绑定的邮箱地址
请输入注册信息