AcWing 135. 最大子序和(单调队列优化DP)
- 一、问题
- 二、分析
- 三、代码
一、问题
二、分析
涉及到一段区间和的问题,我们最容易想到的是前缀和算法。
因此,我们可以假设我们的最优解区间是[l,r][l,r][l,r],同时将前缀和数组记作s[i]s[i]s[i],那么我们的区间和就可以表示为s[r]−s[l−1]s[r]-s[l-1]s[r]−s[l−1],假设我们固定一右端点rrr,那么如果想要让这个差值最大,在s[r]s[r]s[r]不变的情况下,只能是s[l−1]s[l-1]s[l−1]最小。
也就是说,我们需要在rrr的右侧寻找一个最小值,但是这个最小值的所在范围是有一定约束的,因为题目中说的是区间长度不超过mmm,所以我们需要在rrr右侧m+1m+1m+1的范围内找一个最小值。
为什么是m+1m+1m+1?
因为我们想要找[l,r][l,r][l,r]的区间和,用到的是s[l−1]s[l-1]s[l−1],也就是说如果区间长度是mmm,我们减去的是左侧距离m+1m+1m+1的那个数。
而随着右端点的移动,这个长度为mmm的范围也在向右移动,相当于一个滑动窗口。
而对于滑动窗口求最值的方法就是我们在之前介绍过的单调队列。
如果读者对单调队列不熟悉的话,作者在算法专栏中写过一篇文章:第九章:单调栈与单调队列,这里面详细地介绍了该算法和一些关键点的讨论。
这里就直接用单调队列了。
因此,我们只需要枚举右端点,一边枚举右端点一边更新滑动窗口。
这道题其实和DP没有太大的关系。
只不过我们的右端点rrr是从小到大枚举的,类似于DP从小到大求解问题的思想。
但状态和状态之间没有必然的联系。
三、代码
#include<bits/stdc++.h>
using namespace std;
const int N = 3e5 + 10;
int a[N], s[N];
int q[N], h, t;
void solve()
{int n, m;cin >> n >> m;for(int i = 1; i <= n; i ++ ){scanf("%d", a + i);a[i] += a[i - 1];}int res = -0x3f3f3f3f;for(int i = 1; i <= n; i ++ ){if(h <= t && q[h] < i - m )h++;res = max(res, a[i] - a[q[h]]);while(h <= t && a[q[t]] >= a[i])t--;q[ ++ t] = i;}cout << res << endl;
}int main()
{solve();return 0;
}