输入一个长度为n的整数序列,从中找出一段不超过m的连续子序列,使得整个序列的和最大。
容易想到计算区间和,可以转换成两个前缀和相减,用S[i]表示前i项和,则连续子序列[L,R]中的数的和为S[R]-S[L-1].
所以原问题转化为找出两个位置x,y,使得s[y]-s[x]最大,且y-x<=M
暴力枚举O(n*m).
首先枚举右端点r,找左端点l,l范围为[r-m,r-1]
注意到:若k<j<i,且s[k]>=s[j],那么k永远不可能是最佳选择。
以上事实说明,可能成为最佳策略的集合一定是一个下标位置递增,对应的前缀和S的值也递增的序列。
用队列保存这一队列,随着右端点变,从前向后扫描,对每一个i:
1.判断队头决策与i的距离是否超过M的范围
2.此时队头就是右端点为i时,左端点j的最优选择。
3.不断删除队尾决策,直到队尾对应的S的值小于S[i],然后把i作为一个新的决策入队 //维护单调性,删除的都不可能是最优决策
int l=1,r=1; q[1]=0;//初始决策 j=0; for(int i=1;i<=n;i++){ while(l<=r&&q[l]<i-m)l++;//step1 ans=max(ans,sum[i]-sum[q[l]]);//step2 while(l<=r&&sum[q[r]]>=sum[i])r--;//step3 q[++r]=i; }
AC代码
1 #include<bits/stdc++.h> 2 using namespace std; 3 typedef long long ll; 4 ll A[300004]; 5 int n,m; 6 ll q[300005]; 7 ll sum[300004]; 8 int main() 9 { 10 scanf("%d%d",&n,&m); 11 for(int i=1;i<=n;i++){ 12 scanf("%lld",&A[i]); 13 sum[i]=sum[i-1]+A[i]; 14 } 15 ll ans=0; 16 int l=1,r=1; 17 q[1]=0; 18 for(int i=1;i<=n;i++){ 19 while(l<=r&&q[l]<i-m)l++; 20 ans=max(ans,sum[i]-sum[q[l]]); 21 while(l<=r&&sum[q[r]]>sum[i])r--; 22 q[++r]=i; 23 } 24 cout<<ans<<'\n'; 25 }