题目大意:输入一个长度为 n 的整数序列,从中找出一段不超过 m 的连续子序列,使得整个序列的和最大。
输入: 6 4 1 -3 5 1 -2 3
输出:7
1<=n,m<=300000答案小于long long int
------------------------------------------------------
从题目,要序列中某一段的和,那么前缀和少不了的。所以先处理前缀和。第一步想暴力算法:无非就是一直循环:
for(int i = 1; i <= n; i++)
for(int j = i - m; j <= i; j++)
Max = max(Max, sum[i] - sum[j - 1]);
时间复杂度O(nm)。接下来我们想想怎么优化。我们每次循环两遍,都是在求一个值。让f[i]表示循环到i最后取的最大值。根据上述循环可知:
f[i] = max{sum[i] - sum[j - 1]} (i - m <= j <= i)
对于每一次求f[i],sum[i]是确定的。在这个方程中,被减数确定了, 取最大时,被减数取最小。
这样,问题就转化为了在O(1) ~ O(log(n))时间求某一段区间的最值
下一步,我们就有很多方式解决这个问题了:
1.经典的RMQ问题,直接上ST表
2.线段树和树状数组也可以
3.由于每次求的区间的特殊性(每次求的最值区间相较于上一个往右了一格)
所以可以用单调队列(滑动窗口)
最后上代码:
#include <iostream>
#include <cstdio>
using namespace std;
const int maxn = 300006;
int n, m, Max = -0x3f3f3f3f;
int q[maxn], f = 1, r = 1;
int ar[maxn], qzh[maxn];
int main(){
freopen("sum.in","r",stdin);
freopen("sum.out","w",stdout);
cin >> n >> m;
for(int i = 1; i <= n; ++i){cin >> ar[i];qzh[i] = qzh[i - 1] + ar[i];}
for(int i = 1; i <= n; ++i){
while(f != r && i - q[f] > m){
f++;
}
while(f != r && qzh[q[r-1]] > qzh[i]){
r--;
}
q[r++] = i;
Max = max(Max, qzh[i] - qzh[q[f]]);
Max = max(Max, ar[i]);
}
cout << Max;
return 0;
}