题目链接
1.最大子序和问题,应该是比较常见的,但是这道题需要我们求长度不超过m的最大子序列和。首先不考虑这个条件,对于以j为终点的每一个子序列和,假设它以i为起点(i<=j)我们都可以通过两个前缀和的差s[j]-s[i-1]求得,这样s[i-1]一定是s[1],…,s[j-1]中最小的。但是本题的限制条件,长度不超过m,我们显然不能通过维护前面前缀和的最小值来写
2.很容易想到遍历每一个终点的前1~m个数,但是当m较大时,时间复杂度达到了O(n2),3e5的数据范围显然一定超时。对于每一个终点前面的乱序的前缀和,如何快速找到符合条件的最小呢?这里可以使用单调队列
3.使用双端队列,队列中存储每一个终点前面的起点序号。首先无法优化的操作,就是必须遍历队列去找长度不超过m的最小元素,然后更新ans。但是下面的操作就特别重要了,对于当前终点j,我们找到了s[q[?]]是前缀不超过m的最小前缀和,于是,在当前终点的序号入队之前,我们将队列中大于等于s[q[?]]的元素全部删除,可以证明,在第j+1个终点,我们刚刚找到的最小前缀和是有用的,其他比它大的一定都不会被用到。这样之后,相当于维护
了一个单调队列,不难发现,由于每次的删除,队列的每次查找都是很小的常数级别,而且队列中不会有太多的数。和正常求最大前缀和相似,我们首先要向队列中添加0,对于第一个数,只有减去0才不影响第一个最大前缀和
对于样例来看,前缀和为:
1 -2 3 4 2 5
每次队列的存储的下标对应的前缀和,加粗的为有效的最小值为:
0
0 1
-2
-2 3
-2 3 4
-2 2
-2 2 5
#include <iostream>
#include <algorithm>
using namespace std;
#define INF 0x7f7f7f7f
typedef long long ll;
const int maxn=3e5+10;
int s[maxn],q[maxn];
int n,m;
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++){
scanf("%d",&s[i]);
s[i]+=s[i-1];
}
int ans=-INF;
int l=1,r=1;
q[1]=0;
for(int i=1;i<=n;i++){
while(l<=r && q[l]<i-m) l++;
ans=max(ans,s[i]-s[q[l]]);
while(l<=r && s[q[r]]>=s[i]) r--;
q[++r]=i;
}
printf("%d\n",ans);
return 0;
}