【算法•日更•第十七期】信息奥赛一本通1598:【 例 2】最大连续和题解
废话不多说,直接上题:
1598:【 例 2】最大连续和
时间限制: 1000 ms 内存限制: 524288 KB
提交数: 303 通过数: 91
【题目描述】
给你一个长度为 n 的整数序列 {A1,A2,⋯,An},要求从中找出一段连续的长度不超过 m 的子序列,使得这个序列的和最大。
【输入】
第一行为两个整数 n,m;
第二行为 n 个用空格分开的整数序列,每个数的绝对值都小于 1000。
【输出】
仅一个整数,表示连续长度不超过 m 的最大子序列和。
【输入样例】
6 4 1 -3 5 1 -2 3
【输出样例】
7
【提示】
数据范围与提示:
对于 50% 的数据,1≤N,M≤104 ;
对于 100% 的数据,1≤N,M≤2×105 。
【来源】
这道题明显和例一一样。都要用到单调队列优化动态规划,不过也有很多方法,我们来讲一讲。
方法1:暴力
用普通单调队列优化的方法,将k从1枚举到m。
方法2:优先队列优化
时间复杂度依旧很高。
方法3:单调队列优化
我们可以维护一个前缀和。那么这个前缀和是干什么的呢?我们可以依次来差分。
我们需要维护一个i-k ~ i+1的区间(长度为m),找到一个最小的前缀和(这样就可以保证这个区间内的和最大),用pre[i+1]-这个前缀和就是这个区间的最大值了,那么用ans来记录一下最大值即可。
同时要注意一个细节:看到别人的博客都有这样一句话if(n<=m) ans=max(ans,pre[n]);,这是为什么呢?当n=4,k=6时,数字分别是1,1,1,1,这种情况应该输出4,但是程序会输出3,这是为什么呢?因为队列一定不会是空的,也就是说一定会减去1,那么答案就错了。
好了,详见注释,代码如下:
1 #include<iostream> 2 using namespace std; 3 int n,m,a[1000000],num[1000000],q[1000000],pre[1000000],ans; 4 inline void dp() 5 { 6 int head=1,tail=0;//标志队列为空 7 ans=pre[1]; 8 if(n<=m) ans=max(ans,pre[n]);//特判 9 for(int i=1;i<n;i++)//常规操作 10 { 11 while(num[head]<i-m+1&&head<=tail) head++; 12 while(pre[num[tail]]>=pre[i]&&head<=tail) tail--; 13 num[++tail]=i; 14 ans=max(ans,pre[i+1]-pre[num[head]]);//记录ans 15 } 16 } 17 int main() 18 { 19 cin>>n>>m; 20 for(int i=1;i<=n;i++) 21 { 22 cin>>a[i]; 23 pre[i]=pre[i-1]+a[i];//记录前缀和 24 } 25 dp(); 26 cout<<ans; 27 return 0; 28 }