洛谷 P2629 好消息,坏消息 题解

暴力算法的时间复杂度是O(n^2),考虑优化;

先导入一种思想——断环为链。说通俗点就是在原数组后面再接上下标为1——(n - 1)的元素;

以样例为例:-3 5 1 2;我们将其断环为链后可以得到这样的一维数组:-3 5 1 2 -3 5 1;
设其下标为1——7;当k=1时,判断下标1——4;当k=2时,判断下标2——5;当k=3时,判断下标3——6;当k=4时,判断下标4——7 结束;

断环为链后,题目要求就变为寻找k的个数,使k可以满足k——(n + k - 1)中,每个元素的对应的区间内前缀和都是非负的;
对此,我们在使用前缀和预处理后,就只需判断每个s[i] - s[k - 1] (k <= i <= n + k - 1)是否为负就可以;

既然这样,那么是否要判断k——n+k-1中每一个数的和呢?当然不是,因为其中如果只要有一个元素的对应的区间内前缀和是负的,那么这个k就是不符合的;
所以我们只需要判断一次———判断最小的s[i]减去s[k-1]是否为负。

总的来说:首先断环为链,其次前缀和处理,最后维护一个单调队列,找到最小的s[i],判断即可;
代码如下:

#include<cstdio>
#include<iostream>
using namespace std;
int n,head=1,tail,ans;
long long a[2000001],s[2000001],q[2000001];
int main()
{
    scanf("%d",&n);
    for(register int i=1;i<=n;i+=1)
        scanf("%lld",&a[i]);
    for(register int i=1;i<=n-1;i+=1)
        a[i+n]=a[i];
    for(register int i=1;i<=2*n-1;i+=1)
        s[i]=s[i-1]+a[i];
    for(register int i=1;i<=2*n-1;i+=1)
    {
        while(head<=tail&&max(i-n+1,1)>q[head])head++;
        while(head<=tail&&s[i]<=s[q[tail]])tail--;
        q[++tail]=i;
        if(i-n+1>0&&s[q[head]]-s[i-n]>=0)ans++;
    }
    printf("%d\n",ans);
    return 0;
}
posted @ 2021-12-16 21:29  Altwilio  阅读(71)  评论(0编辑  收藏  举报