1172: [视频]单调队列(过渡题)题目
【题意】
给定一个n个数的数列,从左至右输出每个长度为m的数列段内的最大数。
比如8个数的数列[1 3 -1 -3 5 3 6 7],m=3,那么每连续3个最大值如下:
位置 | 最大值 |
[1 3 -1] -3 5 3 6 7 | 3 |
1 [3 -1 -3] 5 3 6 7 | 3 |
1 3 [-1 -3 5] 3 6 7 | 5 |
1 3 -1 [-3 5 3] 6 7 | 5 |
1 3 -1 -3 [5 3 6] 7 | 6 |
1 3 -1 -3 5 [3 6 7] | 7 |
【输入格式】
第一行两个整数n和m( 1<= n <= 20 0000,m<=n)。
下来给出n个整数。
【输出格式】
一行一个整数,表示每连续m个数的最大值。
【样例输入】
8 3
1 3 -1 -3 5 3 6 7
【样例输出】
3
3
5
5
6
7
思路:这道题绝对是单调队列里面最简单的一道题,只是过渡题,还不算是难的,单调队列和栈有点像,然后我也不说太多了因为这个在代码中写的超级详细了
/*单调队列和栈有点像,但是栈是先进后出;单调队列是先进先出
同时,单调队列是成递减的队列的,就是第一个也就是队头是最大的
队尾是最小的,然后无数次删减之后的队列就是要输出的答案
*/
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
struct node
{
int x,p;//x表示的是这个数的值,p表示的是在单调队列中的位置
}list[210000];//记录当前这个数及前面的值和位置
int a[210000];//储存当前这个数
int n,m;
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
list[1].x=-999999999; list[1].p=0;//初始化,因为要求最大值所以要定义最小的值,第一个的位置为0
int head,tail;//队头和队尾
head=tail=1;//都要为1,不然就没有人
for(int i=1;i<=n;i++)//寻找最大值
{
//一开始一定要 head<=tail 才表示这一条队列中有人
while(head<=tail && i-list[head].p>=m) head++;
/*如果包括自己的前10个人不删掉,其他都要删掉,就是从第一个开始删,离自己最远的开始删
也就是说 如果我是 15, 我要求的是 长度为5的队列, 那么包括我自己 15 14 13 12 11,这几个人不该删,因为这个才是完整的队列
而10 也就是 15-m=15-5=10,这个也要删掉因为包括了自己,所以要 >=m, 除了 15 14 13 12 11不用删,其他全部删掉
而 不是 head-- 而是 head++ 是因为 我们 head++就是增加后面的 tail, 如果是 head--,就要将整个队伍前移,自然没那么方便
所以我们要用head--,
总结起来就是 删队头,除了不该删的
*/
while(head<=tail && list[tail].x<=a[i]) tail--;
/*
这一步就是删队尾,更加复杂,head[tail].x也可以表示为自己,因为是从自己开始往前删减,所以才能是队头最大,队尾最小
然后我们从自己的前面开始删,删啊删啊,比自己小的都要删掉,遇到比自己大的就停止,不进行
比如说 总共有9个数 我是第6个 需要一个长度为 3 的队列, 那么 假设 10 20 15 7 9 10 33 24 17,我是第6个,
然后我们经过上一步 就把前面的10 20 15 删掉了,但是他们还在因为我们并没有删,只是增加了结尾(上面提到)
那么这一步就是从自己开始向前寻找,好的 找到了9,比自己小,删掉,这一回是真的删掉,因为这个数到后面就再也没有用了
继续向前看到了7,也比自己小,继续删掉,那么在 7 9 10中 10为最大值,
这个时候 15就不开心了,因为 删完 7 9之后, 15 在 10的前面,按常理讲,应该是要成为 和10在一起的最大值
但是要记住,前面删掉了,如果没有删,15是根本用不到的,所以就是要有前面的那一步才能继续,然后 不能满足 15
所以在 7 9 10中的最大值就是 10, 15自然有人处理
然后处理之后剩下的就是 10 20 15 10 33 24 17,然后一直到所有m队列删完之后的队列就是最终的队列
*/
tail++; list[tail].x=a[i]; list[tail].p=i;
/*
因为前面是计算我自己前面的,所以tail++之后的tail,也就是现在的最后一个就是我自己的值
然后这个最后一个尾巴的位置就是当前循环到的i,也就是我自己的位置
*/
if(i>=m) printf("%d\n",list[head].x);//找到的数大于等于要求的数之后,就可以直接输出最终的队列,
//因为这个队列都是最大的队列,也就是所有队列的队头
}
return 0;
}