洛谷P2629好消息,坏消息
本文最后更新于 155 天前,其中的信息可能已经有所发展或是发生改变。

P2629 好消息,坏消息 – 洛谷

起因是博主看了这道题的题解没几个人用队列来做,所以自己wa了9发搓了一道题解,给大家讲一讲这道题

思路

环变线性 + 维护区间最小值。
1. 环转直线
把数组复制一遍:a[i+n]=a[i]。这样线性数组上从 l开始长为 n 的一段就覆盖了“从 l 出发绕环一周”的所有路径。
对起点 l=1… n,对应的终点是 r=l+n-1,最大会到 2n-1,所以你构建前缀和到了 2n-1 就够了。
2. 判定一个起点是否可行
对于一个起点 l,我们需要检查从 l 开始,连续走 n 个元素,到达 r = l + n – 1 的这段区间 [l, r]。
我们要求在整个路径上,从起点开始的任意一段前缀和都不能为负数。
3. 滑动窗口 + 单调队列维护区间最小值
当窗口右端点 i 从 1 往右滑时:

•   把当前 S(i) 入队,保持队列里对应的 S(.) 单调递增(尾部弹出更大的)。
•   当窗口长度达到 n(即 i >= n)后,窗口为 [l=i-n+1, r=i]。

队头若在窗口外(队头索引 < l)就弹出。此时队头就是 [l,r] 上的最小 S 的位置。
• 检查 min S – S(l-1) >= 0 即可决定该起点是否计数。

#include<bits/stdc++.h>
using namespace std;
const int N = 10000010;
typedef long long LL;
LL a[N], sum[N];
typedef list<int>::iterator Iter;
int main(){
    ios::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);
    deque<int> dq;
    int n, k = 0;
    cin >> n;
    //复制数组到 a[n + 1 ... 2n],把环拉直
    for (int i = 1; i <= n; i ++ ){
        cin >> a[i];
        a[i + n] = a[i];
    }
    //前缀和sum[i] = sum[i - 1] + a[i], 做到2n-1即可
    for (int i = 1; i <= 2 * n - 1; i ++ ) sum[i] = sum[i - 1] + a[i];
    //单调队列维护当前窗口[1...i]内的最小sum
    for (int i = 1; i <= 2 * n - 1; i ++ ){
        while (!dq.empty() && sum[dq.back()] > sum[i]) dq.pop_back();
        dq.push_back(i);
        if (i >= n) {
            int l = i - n + 1;//当前窗口
            int r = i;
            while (!dq.empty() && dq.front() < l) dq.pop_front();
            LL mn = sum[dq.front()];//窗口内最小的s[i]
            LL base = sum[l - 1];//s[l - 1]
            if (mn - base >= 0) k ++;//满足条件则该起点有效
        }
    }
    cout << k;
    return 0;
}

几个关键点

  1. 为什么要计算到 2n – 1

因为我们需要检查所有可能的起点 l(从 1 到 n),而每个起点对应的区间是 [l,\, l+n-1]。
当 l = n 时,终点就是 r = l+n-1 = 2n-1,所以只需要把前缀和计算到 2n-1 就足够覆盖所有长度为 n 的区间。

  1. 为什么比较最小值与 S(l-1)

我们要求从起点 l 出发的连续 n 个数中,任意前缀和都不能小于零。
公式是:
在(l,r)中min(S(i)-S(l-1))>=0
这意味着,只要这段区间的最小前缀和 S(i) 都不小于起点之前的前缀和 S(l-1),就可以保证从 l 出发的整个过程不会出现负值。
因此,判断一个起点是否合法,只需要比较:
在(l,r)中min(S(i)>=S(i-1)

  1. 单调队列的作用

为了高效地找到每个窗口 [l,r] 的 最小前缀和,我们用一个单调递增队列来维护候选下标:
• 队列中存的是下标,而不是值;
• 队列内对应的 S(.) 值严格递增;
• 这样,队头位置就永远是当前窗口内最小的前缀和所在的位置。

当窗口右端点向右移动时:
• 如果新加入的前缀和比队尾大,就直接入队;
• 如果新前缀和比队尾小,就把队尾弹出,保持单调递增;
• 如果队头已经滑出窗口,就把队头弹出。

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇