好消息,坏消息(洛谷P2629)单调队列维护最小前缀和

这道题在我冥思苦想下,终于扫清所有障碍
我娓娓道来!
在这里插入图片描述
首先分析题目
有n条消息,必须按照顺序念(其中可以选出第k条消息,从k念到n,在从1念到k-1),每条消息有好有坏,好消息老板心情变好,加一个正数。坏消息,心情变差,减去一个正数,我们要让老板的心情永远大于0,保证我们不会被炒鱿鱼!

知识点讲解
1、环化
将n条消息变成一个环,我可以从环上找到任意一点念n条消息。(保证消息不重复且按照一定顺序)那么如何变成一个环呢,我们可以将数组复制一份,连接到数组尾部。准确来说复制n-1条消息,我们来举个例子!
-3 5 1 2
从头复制n-1条消息链接到尾部
-3 5 1 2 -3 5 1
从3取 -3 5 1 2
从5取 5 1 2 -3
从1取 1 2 -3 5
从2取 2 -3 5 1
一共四种情况。这就是环化!

2、前缀和
代码形式sum[i]=sum[i-1]+a[i];
举例:求环化后的前缀和
-3 5 1 2 -3 5 1 (求前缀和)
-3 2 3 5 2 7 8

3、最小前缀和
直接举例
从3取 -3 5 1 2 求前缀和 -3 2 3 5最小前缀和-3
从5取 5 1 2 -3 求前缀和 5 6 8 5最小前缀和5
(注意最小前缀和5,落在对应-3头上,而不是第一个5)
从1取 1 2 -3 5 求前缀和 2 3 0 5最小前缀和0
从2取 2 -3 5 1求前缀和 2 -1 4 5最小前缀和-1

4、一个结论
(可以跳过先看整体思路)
这个结论是我针对这道题总结出来的,不知是否有普适性!有了这个结论,大家一定会恍然大悟!~~~~
前缀和最小的点,一定是某段最小前缀和
(该点一定落在段中)
举例说明:
-3 5 1 2 -3 5 1
前缀和:
-3 2 3 5 2 7 8
对应下标:
1 2 3 4 5 6 7
下标1的-3是前缀和中最小的点,同时也是-3 5 1 2 这段的最小前缀和!
下标5对应的2是除了-3外,最小的前缀和的点,同时也是 5 1 2 -3,1 2 -3 5,
2 -3 5 1这几段中的最小前缀和取到的下标!
我搞个图给大家,不要懵掉!

在这里插入图片描述
如果看完图片还没有明白我们跳过,先看整体思路!

整体思路
我们通过环链,求1到2*n-1的前缀和,我们每次选取k,通过判断i(i>k&&i<n+k),s[i]-s[k-1]也就是从k到i的和,我们判断这个和是否<0,如果小于0这段就pass掉,选取其他k!但是我们要遍历每一个i!
我们能不能对上述思路在进行优化呢,答案是可以的,我们选出k到n+k-1中(也就是k所对应的那段)最小的s[i]去减去s[k-1]判断是否<0,即可得到答案!
但是大家有没有疑惑,最小的s[i]一定是这段最小的前缀和么?
通过我们的结论就可以断定,最小的s[i]一定是最小的前缀和!
对于k到k+n+1中的最小值,我们可以用单调队列来维护!

附上代码
代码中有细节大家慢慢看!

在这里#include<bits/stdc++.h>
using namespace std;
int n;//n条消息
int a[2000005];//要形成环,所以要开二倍
int sum[2000005];
deque<int> q;//存放最小前缀和的下标

int main()
{
    scanf("%d",&n);//输入n条消息
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&a[i]);//不要忘记&取位符号
        a[i+n]=a[i];//形成环,因为n+1条消息和第1条是相同的,依此类推!
    }
    //求出前2n-1个前缀和
    for(int i=1;i<=2*n-1;i++)
    {
        sum[i]=sum[i-1]+a[i];
    }

    int ans=0;
   //遍历所有前缀和,
  for(int i=1;i<=2*n-1;i++)
  {
      //while循环找到最小前缀和,只要队尾的前缀和大于当前的前缀和,就pop出队尾元素!
      while(!q.empty()&&sum[q.back()]>=sum[i])
      {
          q.pop_back();
      }
      q.push_back(i);//添加进队尾
      //只有当i>=n时,才能满足有n条消息
      if(i>=n)
      {
          //while循环保证最小前缀和下标,一定在当前的n条消息中
         while(!q.empty()&&q.front()<=i-n)
         {
             q.pop_front();
         }
         if(sum[q.front()]-sum[i-n]>=0)ans++;//保证最小前缀和减去sum[k-1]大于0
      }
  }
   printf("%d",ans);
    return 0;
}
插入代码片

感谢大家观看!
我说的有点啰嗦,逻辑有点不好,大家多多见谅!
这道题实在不好表达,尤其是我那个结论,当时我就卡住了,最小s[i]一定是最小前缀和么,以及给出了所有前缀和,如何求出每一段,最小前缀和。我反复实验终于搞明白!发现其实挺简单的,但是就是想不明白!
大家多多点赞,谢谢支持!

  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值