输入一个长度为n的整数序列,从中找出一段不超过M的连续子序列,使得整个序列的和最大。
例如 1,-3,5,1,-2,3
当m=2或m=3时,S=5+1=6
input
第一行:n,m
第二行:n个数
output
最大的和sum
题目分析:首先我们知道O(n)可以很简单的解决原题(不限制子序列的长度)。但是这题加了限制(长度不超过m),并且数据为300000,并不能用O(n*m)的暴力计算最大前缀和之差的方法。所以要用到一些特殊的姿势~~~单调队列。
首先我们分析知道 f[i]=max(sum[i]-s[i-k]),k∈[1,m]。但是一般的维护只能是O(n*m)。变成f[i]=sum[i]-min(sum[i-k]),k∈[1,m]。我们只需要维护一个宽度为m的范围内的sum值的队列,然后每次用最小的去更新f[i],再将sum[i]也加入队列。用优先队列的思想,每个sum最多只能进一次队列出一次队列,所以复杂度是O(2*n)。
#include<iostream> #include <bits/stdc++.h> using namespace std; int a[300100]; long long s[300100],f[300100],x[300100],w[300100]; int main() { int i,n,m,l,r; long long ans=0; scanf("%d%d", &n, &m); memset(f,0,sizeof(f)); memset(s,0,sizeof(s)); for (i=1;i<=n;i++) { scanf("%d", &a[i]);s[i]=s[i-1]+a[i]; } l=1;r=1;x[1]=0;w[1]=0; for (i=1;i<=n;i++) { while (w[l]<i-m) l++; f[i]=s[i]-s[w[l]]; while (s[i]<x[r]&&r>=l) r--; r++; x[r]=s[i];w[r]=i; } for (i=1;i<=n;i++) if (f[i]>ans) ans=f[i]; printf("%I64d\n", ans); return 0; }