直接把题目化成数学语言,一步一步化简就能做出来了。
假设原序列为a,f为其前缀和(因为涉及区间求和所以用前缀和快)
i∈[1,n]
ans=max{f[i]-f[i-1],f[i]-f[i-2],...,f[i]-f[i-m]}
→ ans=f[i]-min{f[i-1],f[i-2],...,f[i-m]}
令s[i]=min{f[i-1],f[i-2],...,f[i-m]}
得ans=f[i]-s[i]
根据i∈[1,n]和min{f[i-1],f[i-2],...,f[i-m]}
可以发现这个东西可以有两个理解:
1、滑动窗口求最小值
2、区间最小值(线段树)
滑动窗口代码短而且好写,所以采用第一种理解。
因为滑动窗口处理的时候是i下标的进去一个就进行处理,而s[i]需要的窗口是从i-1开始的,所以对公式进行+1
得到s[i+1]=min{f[i],f[i-1],...,f[i-m+1]},同时i∈[0,n-1]
初始化的时候有地方要注意,假设i==1,那么ans=max{f[1]},并不需要减去什么东西(可以认为是0),包括i==2的时候,ans=max{f[2]-f[1],f[2]},所以要提前push一个0进去,当做第一个窗口未形成时候的减数。
AC代码
#include <bits/stdc++.h>
using namespace std;
const int N=3e5+1;
int n,m,a[N],f[N],s[N],ans=INT_MIN;
deque<pair<int,int>>q;//index,value
int main(){
for(int i=1 and cin>>n>>m;i<=n and cin>>a[i];++i)f[i]=f[i-1]+a[i];//压行输入
q.push_back({1,0});
for(int i=0;i<n;++i){
while(!q.empty() and f[i]<q.back().second)q.pop_back();
while(!q.empty() and q.front().first<i-m+1)q.pop_front();
q.push_back({i,f[i]});
s[i+1]=q.front().second;
}
for(int i=1;i<=n;++i)ans= max(ans,f[i]-s[i]);
cout<<ans;
return 0;
}
很明显不卡常数,所以用线段树去求区间极值也是可以过的。