题目链接
https://www.acwing.com/problem/content/137/
输入一个长度为n的整数序列,从中找出一段长度不超过m的连续子序列,使得子序列中所有数的和最大。
输入格式
第一行输入两个整数n,m。
第二行输入n个数,代表长度为n的整数序列。
同一行数之间用空格隔开。
输出格式
输出一个整数,代表该序列的最大子序和。
数据范围
1≤n,m≤300000
输入样例:
6 4
1 -3 5 1 -2 3
输出样例:
7
题解
题解参考李煜东的算法竞赛进阶指南
用sum数组保存前缀和,那么区间和就转化为两个个前缀和相减。
首先我们枚举右端点i,当i固定时,问题就变为:找到一个左端点j,其中j属于[j-m,i-m]并且是sum[j]最小。
不妨比较一下任意两个位置j和k,如果k<j<i并且sum[k]>=sum[j],那么对于所有大于等于i的右端点,k永远不会成为最优选择,这是因为不但sum[k]不小于sum[j],而且j离i更近,长度更不容易超过m,即j的生存能力比k更强。所以当j出现后,k是一个完全无用的位置。
以上比较告诉我们,可能成为最优选择的策略集合一点哪个是一个“下标位置递增,对应前缀和sum的值也递增”的序列。我们可以用一个碎裂保存这个序列。随着右端点变从前向后扫描,我们对每个i执行以下三个步骤:
1.判断对头决策与i的距离是否超过M的范围,若超出则出队。
2.此时对头就是右端点i时,左端点j的最优选择。
3.不断删除队尾决策,直到队尾对应的sum值小于是sum[j].然后把i作为一个新的决策入队。
代码
用q数组模拟队列,队列里存的是下标,head是队头,tail是队尾。队列里的元素是单调递增的。head对应的值最小
#include<iostream>
#include<algorithm>
using namespace std;
const int maxn=3e5+5;
typedef long long ll;
int sum[maxn];
int q[maxn];
int main(){
int n,m;
cin>>n>>m;
for(int i=1;i<=n;i++){
cin>>sum[i];
sum[i]+=sum[i-1];
}
int head=1,tail=1;
q[head]=0;
int ans=sum[1];
for(int i=1;i<=n;i++){
if(head<=tail&&q[head]<i-m) head++; //步骤1
ans=max(ans,sum[i]-sum[q[head]]); //步骤2
//步骤3
while(head<=tail&&sum[q[tail]]>=sum[i]) tail--;
q[++tail]=i;
}
cout<<ans<<endl;
return 0;
}