[Luogu P1886]滑动窗口--单调队列入门

本文介绍了一种高效算法,利用单调队列解决滑动窗口中最大值和最小值的问题。通过实例讲解了算法原理及实现过程,适用于数据范围大的场景。

题目描述

现在有一堆数字共N个数字(N<=10^6),以及一个大小为k的窗口。现在这个从左边开始向右滑动,每次滑动一个单位,求出每次滑动后窗口中的最大值和最小值。

例如:

The array is [1 3 -1 -3 5 3 6 7], and k = 3.

输入输出格式

输入格式:

 

输入一共有两行,第一行为n,k。

第二行为n个数(<INT_MAX).

 

输出格式:

 

输出共两行,第一行为每次窗口滑动的最小值

第二行为每次窗口滑动的最大值

 

这题是单调队列入门题。题意清晰明了,求区间最大(小)。第一反应是线段树或者RMQ,但是数据范围爆炸。这道题和普通的区间的区别就在于它的区间长度是固定的。所以使用时间复杂度为O(n)的单调队列来解决。

什么是单调队列呢?单调队列是一种特殊的双端队列,其内部具有单调性(有点像优先序列,但有着本质区别)。

如何实现插入?从队尾插入,若破坏了单调性,则删除队尾元素(这个删除决定了什么题能用什么题不能用),直到找到一个不影响的位置。

如何实现输出?访问队首(真是方便)

如何解决这道题?首先设一个区间为(l,r),则有max(l+1,r+1)=max(a[r+1],max(a[l+1],a[l+2]...a[r])),那么max(l+1,r+1)与max(l,r)其实是有很大一部分重叠的,那么在问题实现的时候就只需要先删除单调队列中不在区间里的数(a[l]),再插入新数(a[r+1]),剩余的不变,就可以解决了。

代码:

 1 #include<iostream>
 2 #include<cstdio>
 3 #include<algorithm>
 4 using namespace std;
 5 inline int read(){
 6     int res=0,f=1;
 7     char ch=getchar();
 8     while(ch<'0'||ch>'9'){
 9         if(ch=='-')f=-1;
10         ch=getchar();
11     }
12     while(ch>='0'&&ch<='9'){
13         res=res*10+(ch-'0');
14         ch=getchar();
15     }
16     return res*f;
17 }
18 const int N=1e6+5;
19 int nsf[N],nbf[N],que[N],number[N],a[N];
20 int n,k,head,tail;
21 inline void INT(){
22     n=read();k=read();
23     for(int i=1;i<=n;++i)a[i]=read();
24 }
25 inline void findmin(){
26     head=1;tail=0;//队头、尾初始化 
27     for(int i=1;i<=n;++i){//插入a[i]到单调序列 
28         while(number[head]<i-k+1&&head<=tail)++head;
29         //从队首开始找,“过时”的删除 
30         while(a[i]<=que[tail]&&head<=tail)--tail;
31         //插入时从队尾插入,维护单调上升性质 
32         number[++tail]=i;que[tail]=a[i];
33         //number保存插入时的“时间戳”,que表示值 
34         nsf[i]=que[head];//当前队列中最小值 
35     }
36 }
37 inline void findmax(){
38     head=1;tail=0;
39     for(int i=1;i<=n;++i){
40         while(number[head]<i-k+1&&head<=tail)++head;
41         while(a[i]>=que[tail]&&head<=tail)--tail;
42         number[++tail]=i;que[tail]=a[i];
43         nbf[i]=que[head];
44     }
45 }
46 int main(){
47 INT();//输入 
48 findmin();//动态规划求单调队列最小值 
49 findmax();
50 for(int i=k;i<=n;++i)printf("%d ",nsf[i]);
51 cout<<endl;
52 for(int i=k;i<=n;++i)printf("%d ",nbf[i]);
53 return 0;
54 }

第一次写随笔还有点小兴奋呢~

转载于:https://www.cnblogs.com/clockwhite/p/10540868.html

<think>好的,用户让我介绍一下CSP-J复赛真题中的队列考题。首先,我需要确定用户的需求是什么。CSP-J是面向青少年的计算机能力认证,复赛通常涉及算法数据结构,队列是其中常见的一个考点。用户可能正在准备这个考试,或者想了解这类题目的类型和解题思路。 首先,我应该回忆一下CSP-J历年的真题,尤其是复赛部分涉及队列的题目。比如,2021年的复赛真题中有一道“小熊的果篮”问题,这题就涉及队列的应用。此外,可能还有其他年份的题目,比如2019年或2020年是否也有类似的题目? 接下来,我需要整理这类题目的共同特点。队列通常用于处理先进先出的问题,比如排队、广度优先搜索(BFS)等。在CSP-J的题目中,队列的应用可能涉及到模拟过程,比如处理水果篮的取出顺序,或者类似的需要按顺序处理元素的问题。 然后,我需要具体分析一个例子,比如“小熊的果篮”这道题。题目大意是小熊有一个由n个水果组成的果篮,包括苹果和橘子,排成一排。每天,从左到右,每一段连续的同种水果的第一个会被取出,直到全部取完。要求输出每天取出的水果编号。这题的关键在于如何高效模拟每天取水果的过程,队列在这里可能被用来管理当前剩下的水果,或者记录待处理的段。 解题思路方面,可能需要维护多个队列,或者使用双指针来跟踪当前段的位置。例如,每次处理时,找到所有连续同种水果的段,取出每段的第一个,然后将剩余的部分重新组织。这个过程可能需要多次迭代,直到所有水果被取完。这里队列可以帮助按顺序处理这些段,尤其是在处理动态变化的剩余水果时,队列的结构能够有效管理这些信息。 此外,还需要考虑时间复杂度的优化,因为直接模拟每一天的过程可能会导致超时,尤其是当数据量较大时。例如,使用队列来记录当前有效的段,每次处理时只遍历这些段,而不是整个数组,这样可以减少不必要的重复检查。 另一个可能的队列题目是类似于“约瑟夫环”的问题,或者需要处理排队中的某些规则,比如优先级的调整。但在CSP-J中,可能更倾向于基础的队列应用,如BFS或模拟过程。 总结来说,队列在CSP-J复赛真题中的应用通常涉及模拟问题,需要考生熟练掌握队列的基本操作,并能够将其灵活运用到具体的问题情境中。解题时要注意时间效率,避免暴力模拟导致超时,合理设计数据结构算法流程是关键。</think>CSP-J复赛中的队列相关考题通常考察对队列数据结构的理解及实际问题建模能力。以下以2021年CSP-J复赛真题**T3:小熊的果篮**为例进行解析: --- ### **题目概述** 小熊的果篮有`n`个水果(苹果/桔子),排列成一行。每天会进行以下操作: 1. 将当前所有**连续同种类水果段**的**第一个**水果取出 2. 重复操作直到果篮为空 要求输出每天被取出的水果编号序列。 --- ### **输入输出示例** **输入:** ``` 8 1 0 1 1 0 0 1 1 ``` **输出:** ``` 1 3 5 2 7 4 6 8 5 8 6 7 ``` --- ### **核心解题思路** 1. **队列维护有效区间** - 使用结构体记录每个连续段的`[左边界, 右边界, 水果类型]` - 用队列存储当前有效的连续段 2. **双指针预处理** 先遍历数组,预处理出所有初始的连续段: ```cpp struct Block { int l, r, type; }; queue<Block> q; // 预处理示例 for(int i=1; i<=n; ){ int j = i; while(j<=n && a[j]==a[i]) j++; q.push({i, j-1, a[i]}); i = j; } ``` 3. **模拟每日操作** - 记录当天要删除的位置 - 合并删除后产生的新连续段 ```cpp vector<int> today; // 记录当天要删除的编号 queue<Block> newQ; // 新队列 while(!q.empty()){ Block cur = q.front(); q.pop(); // 取出当前段的第一个元素 today.push_back(cur.l); cur.l++; // 如果段仍有剩余,放入待处理队列 if(cur.l <= cur.r) tmpQ.push(cur); } ``` 4. **合并相邻同类型段** 扫描临时队列,合并相邻的同类段: ```cpp while(!tmpQ.empty()){ Block cur = tmpQ.front(); tmpQ.pop(); // 合并相邻同类型段 if(!newQ.empty() && newQ.back().type == cur.type){ Block last = newQ.back(); newQ.pop(); newQ.push({last.l, cur.r, cur.type}); }else{ newQ.push(cur); } } ``` --- ### **时间复杂度优化** - **O(n)时间复杂度**:每个水果最多入队出队两次(初始预处理 + 合并操作) - **避免暴力模拟**:直接遍历数组模拟会达到O(n²)复杂度,无法通过全部测试点 --- ### **关键考点** 1. **队列的灵活应用**:通过队列维护动态变化的连续段 2. **合并相邻区间的处理**:保证后续操作的正确性 3. **输出格式控制**:注意每天输出的空格换行 --- ### **练习建议** 1. 完成[洛谷P7910](https://www.luogu.com.cn/problem/P7910)官方数据 2. 尝试其他队列经典问题: - 约瑟夫环问题(循环队列) - 滑动窗口最大值(双端队列) - 广度优先搜索(BFS)基础应用 建议在编写代码时注意边界条件处理,使用`STL queue`或手写队列均可,但要注意避免数组越界。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值