- 求连续子序列的和,必然采用前缀和的思想。
- 所有连续子序列的最大值,肯定是至少以某一结点i为头/尾扫一遍整个序列。
- 但是对于某一个序列尾结点i,我们之前枚举尾结点的时候已经扫过i前面的数了,再扫一遍岂不是很丑陋的O(n^2)。
- 单调队列就是用来优化这种问题的方法。
对于从左到右扫描的 k < j < i ,如果S[j] <= S[k],对于右节点 i ,完全没必要再考虑k了。一是因为 同样的 S[i] 减去更小的 左端点前缀和 S[j] 得到的 序列和 肯定更大;二是因为 j 更靠右边,存活的时间更长。
因此,在我们从左到右扫描的过程中,采用一个队列来保存 扫描过的 结点 i ,要求S[i] 单调递增。
而在寻找最大连续子区间的时候,只需要找到队列首位(且满足序列长度小于等于M)的那个,就一定是左端点的最优解。由于每个元素只入队/出队一次,所以时间复杂度是O(N)。
单调队列的优化思想是:在O(n)的时间复杂度下,减少另一端点的枚举范围,使时间复杂度从O(n^2)降低到O(n),妙就妙在维护队列的单调性不需要太多多余操作,只需将队列元素和当前元素对比并判断是否出队即可。
#include<bits/stdc++.h>
using namespace std;
int n,m;
const int N = 3e5+10;
int a[N];
int sum[N];
int q[N];
int main()
{
scanf("%d %d",&n,&m);
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
sum[i] = sum[i-1] + a[i];
}
int hh = 0,tt = 0;
int res = -0x3f3f3f3f;
for(int i=1;i<=n;i++)
{
while(hh<=tt && (i-q[hh])>m) hh++; //注意前缀和的性质!,sum[i] - sum[q[hh]]表示的是区间,[q[hh]+1,i],所以这里的闭区间算长度不加1!
res = max(res, sum[i] - sum[q[hh]]);
while(hh<=tt && sum[q[tt]] >= sum[i]) tt--;
q[++tt] = i;
}
cout<<res<<endl;
return 0;
}