CH 1201 - 最大子序和 - [单调队列]

题目链接:传送门

描述
输入一个长度为n的整数序列,从中找出一段不超过m的连续子序列,使得整个序列的和最大。

例如 $1,-3,5,1,-2,3$。

当 $m=4$ 时,$S=5+1-2+3=7$;
当 $m=2$ 或 $m=3$ 时,$S=5+1=6$。

输入格式
第一行两个数 $n,m(n,m \le 300000)$
第二行有 $n$ 个数,要求在 $n$ 个数找到最大子序和

输出格式
一个数,数出他们的最大子序和

样例输入
6 4
1 -3 5 1 -2 3
样例输出
7

 

题解:

设这 $n$ 个数的前缀和为 $s_1 \sim s_n$。考虑枚举右端点 $i$,当右端点 $i$ 固定时,显然连续子序列的和为 $s_i - s_{j-1}$。

因此相当于我们要寻找一个点 $y$,使其满足 $y \in [i-m, i-1]$ 的前提下 $s_y$ 最小。

显然,当对于满足 $i-m \le x < y < i$ 的两个点 $x,y$,如果 $s_x \ge s_y$,显然 $x$ 点不能成为最优解,因此可以直接舍弃掉 $x$。

因此,用一个双端队列维护 $j$,队列中的 $j$ 满足随着下标的递增,相应的 $s_j$ 也是递增的。

这样一来,每次对于 $i$ 寻找 $j$,只要从头部抛去 $s_$ 点,剩下来的队头就是最优的 $j$。 

而当前 $i$ 入队,为了保持队列的递增性,因此要在队尾抛去所有 $s_{back} \ge s_i$ 的点。

时间复杂度:所有点出入队一次,$O(n)$。

 

AC代码:

#include<bits/stdc++.h>
using namespace std;
const int INF=0x3f3f3f3f;
const int maxn=300000+10;
int n,m,ans;
int a[maxn],s[maxn];
deque<int> Q;
int main()
{
    std::ios::sync_with_stdio(false);
    cin>>n>>m;
    ans=-INF, s[0]=0;
    Q.push_back(0);
    for(int i=1;i<=n;i++)
    {
        cin>>a[i], s[i]=s[i-1]+a[i];
        while(!Q.empty() && Q.front()<i-m) Q.pop_front();
        ans=max(ans,s[i]-s[Q.front()]);
        while(!Q.empty() && s[Q.back()]>=s[i]) Q.pop_back();
        Q.push_back(i);
    }
    cout<<ans<<endl;
}

 

posted @ 2018-11-08 15:58  Dilthey  阅读(703)  评论(0编辑  收藏  举报