华师一附中OI组

 找回密码
 立即注册
搜索
热搜: 活动 交友 discuz
查看: 1773|回复: 7
打印 上一主题 下一主题

单调队列练习题

[复制链接]

738

主题

1485

帖子

5420

积分

管理员

Rank: 9Rank: 9Rank: 9

积分
5420
跳转到指定楼层
楼主
发表于 2018-5-13 00:43:08 | 只看该作者 回帖奖励 |正序浏览 |阅读模式
题目描述
【题意】
给定一个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

回复

使用道具 举报

738

主题

1485

帖子

5420

积分

管理员

Rank: 9Rank: 9Rank: 9

积分
5420
8#
 楼主| 发表于 2018-8-27 09:21:32 | 只看该作者
单调队列顾名思义就是一个有规律的队列,这个队列的规律是:所有在队列里的数都必须按递增(或递减)的顺序列队,如果真有这么一个队列,那么队列的头是不是就是最小(或最大)的呢?
看看顶楼的那个题目,我们朴素的想法就是解法1:按照常规方法,我们在求fi即i~i+m-1区间内的最值时,要把区间内的所有数都访问一遍,时间复杂度约为O(nm)。有没有一个快一点的算法呢?
解法2:我们知道,上一种算法有一个地方是重复比较了,就是在找当前的f(i)的时候,i的前面k-1个数其它在算f(i-1)的时候我们就比较过了。那么我们能不能保存上一次的结果呢?当然主要是i的前k-1个数中的最大值了。这就要用到单调递减队列。

使用单调队列就涉及到去头和删尾:

1、队列的头一定是在一段时间前就加入了队列,现在的队列头会不会离开了我们处理的区间呢?如果它离我们正在处理的i太远了,我们就要把它去掉,去除冗杂的信息。

2、为了保证队列的递减性,在从列队尾新插入元素v时,要考虑队列尾的值是否大于v,如果是,队列呈现 队列尾-1的值 > 队列尾的值 > v ,此时队列递减性没有消失;如果不是,队列呈现 队列尾-1的值 > 队列尾的值 < v ,队列递减性被打破。

为了维护递减性,我们做如下考虑:v是最新值,它的位置是目前最靠后的,它可成为以后的最大值,必须留下;队列尾-1的值与v大小不定,不能冒然删去它;队列尾的值夹在v和队列尾-1之间,它不但不是最大值,对于以后的情况又不如v优,因为v相比队列尾更靠后(v可以影响到后m个值,队列尾只能影响到从它的位置往后数m-1个值),而且值更大,所以删队列尾是必定的。

在维护好一个 区间正确、严格递减 的单调递减队列后,队列头就是当前区间的最大值了。
  1. #include<cstdio>
  2. #include<cstring>
  3. #include<algorithm>
  4. using namespace std;

  5. int a[200000];
  6. struct node
  7. {
  8.     int x,p;
  9.     node(){}
  10.     node(int xx,int pp){x=xx;p=pp;}
  11. }list[200000];

  12. int main()
  13. {
  14.     int n,m;
  15.     scanf("%d%d",&n,&m);
  16.     for(int i=1;i<=n;i++) scanf("%d",&a[i]);
  17.     int head=1,tail=1;
  18.     list[1]=node(a[1],1);
  19.     for(int i=2;i<=n;i++)
  20.     {
  21.         while(head<=tail&&list[tail].x<=a[i]) tail--;//删尾
  22.         list[++tail]=node(a[i],i);//得到最优解并插入
  23.         while(i-list[head].p>=m) head++;//去头
  24.         if(i>=m) printf("%d\n",list[head]);
  25.     }
  26.     return 0;
  27. }
复制代码

整理归纳单调队列的定义:

1、维护区间最值;
2、去除冗杂状态;
3、保持队列单调(最大值是单调递减序列,最小值是单调递增序列);
4、最优选择在队首。



整理归纳单调队列的使用方法:

1、维护队首(对于上题就是如果你已经是当前的m个之前那你就可以被删了) ;
2、在队尾插入(每插入一个就要从队尾开始往前去除冗杂状态) ;
3、取出需要的最优解(队列头的值即是);
4、借助最优解,得到目前所求的最优解(通常此处插入DP方程)。



单调队列的原理:

在处理fi时,去除冗杂、多余的状态,使得每个状态在队列中只会出现一次;同时维护一个能瞬间得出最优解的队列,减少重新访问的时间;在取得自己所需的值后,为后续的求解做好准备。
回复 支持 反对

使用道具 举报

738

主题

1485

帖子

5420

积分

管理员

Rank: 9Rank: 9Rank: 9

积分
5420
7#
 楼主| 发表于 2018-5-13 19:52:28 | 只看该作者
回复 支持 反对

使用道具 举报

738

主题

1485

帖子

5420

积分

管理员

Rank: 9Rank: 9Rank: 9

积分
5420
6#
 楼主| 发表于 2018-5-13 00:47:07 | 只看该作者
练习:


POJ2823

POJ1156

HDU3041

POJ3017
回复 支持 反对

使用道具 举报

738

主题

1485

帖子

5420

积分

管理员

Rank: 9Rank: 9Rank: 9

积分
5420
5#
 楼主| 发表于 2018-5-13 00:46:56 | 只看该作者
题目描述
【题意】
给一个N*M的数矩阵
现在求一个子矩阵 要求子矩阵中最大值与最小值的差<=C。而且子矩阵的宽度(横)不超过100(长(竖)没有限制)。 求子矩阵的最大面积。
【输入格式】
第一行两个整数 M(左右方向),N(上下方向)和 C  (N,M<=500 0<=C<= 10 )
接下来 N行 每行M个数 每个数(-30000~30000)
【输出格式】
子矩阵的最大面积
【样例输入】
10 15 4
41 40 41 38 39 39 40 42 40 40
39 40 43 40 36 37 35 39 42 42
44 41 39 40 38 40 41 38 35 37
38 38 33 39 36 37 32 36 38 40
39 40 39 39 39 40 40 41 43 41
39 40 41 38 39 38 39 39 39 42
36 39 39 39 39 40 39 41 40 41
31 37 36 41 41 40 39 41 40 40
40 40 40 42 41 40 39 39 39 39
42 40 44 40 38 40 39 39 37 41
41 41 40 39 39 40 41 40 39 40
47 45 49 43 43 41 41 40 39 42
42 41 41 39 40 39 42 40 42 42
41 44 49 43 46 41 42 41 42 42
45 40 42 42 46 42 44 40 42 41
【样例输出】
35
回复 支持 反对

使用道具 举报

738

主题

1485

帖子

5420

积分

管理员

Rank: 9Rank: 9Rank: 9

积分
5420
地板
 楼主| 发表于 2018-5-13 00:46:36 | 只看该作者
题目描述
【题意】
一个猴子要吃香蕉,一共N棵香蕉树排列在一条直线上,它一开始在第一棵树上。
每棵树上有不同数量的香蕉,猴子每次最多的跳跃距离为D,而且最多只能跳M次,问猴子最多能吃到多少香蕉?
【输入格式】
第一行 三个整数 N,D,M (M<N<=5000,D<=10000)
下面N行 每行两个整数 ai,bi (ai,bi<=1000000) 分别表示每棵树上的香蕉数目,以及每棵树的位置(树的位置是递增的)
数据保证没有两棵香蕉树在同一位置,以及b1=0
【输出格式】
一个整数,表示猴子最多吃到的香蕉数。
【样例输入】
5 5 2
6 0
8 3
4 5
6 7
9 10
【样例输出】
20
回复 支持 反对

使用道具 举报

738

主题

1485

帖子

5420

积分

管理员

Rank: 9Rank: 9Rank: 9

积分
5420
板凳
 楼主| 发表于 2018-5-13 00:46:14 | 只看该作者
题目描述
【题意】
一个数列有N(1 <= N <= 100,000)个数(0<=ai<=10^9)。
开始选数,要求选出来的数和最大。
限制:不能选中超过连续M(M<=N)个。  
【输入格式】
第一行两个整数N和M;
下来给出n个数。
【输出格式】
一行一个整数,表示可以得到的最大的和。
【样例输入】
5 2
1
2
3
4
5
【样例输出】
12
回复 支持 反对

使用道具 举报

738

主题

1485

帖子

5420

积分

管理员

Rank: 9Rank: 9Rank: 9

积分
5420
沙发
 楼主| 发表于 2018-5-13 00:43:47 | 只看该作者
题目描述
【题意】
有N个数(N<=100000),在连续M(M<=N)个数里至少要有一个数被选择。
求选出来数的最小总和。
【输入格式】
第一行两个整数 N,M
接下来N行 ai(ai<=100)表示第i个数
【输出格式】
一个整数,最小总和
【样例输入】
5 3
1
2
5
6
2
【样例输出】
4
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

QQ|Archiver|手机版|小黑屋|服务支持:DZ动力|华师一附中OI组  

GMT+8, 2024-11-1 20:19 , Processed in 0.108963 second(s), 25 queries .

Powered by Discuz! X3.2

© 2001-2013 Comsenz Inc.

快速回复 返回顶部 返回列表