当前位置: 首页 > news >正文

[单调队列] day.1

单调队列的操作

举例

不妨用一个问题来说明单调队列的作用和操作:
不断地向缓存数组里读入元素,也不时地去掉最老的元素,不定期的询问当前缓存数组里的最小的元素。
最直接的方法:普通队列实现缓存数组。
进队出队都是O(1),一次查询需要遍历当前队列的所有元素,故O(n)。

用堆实现缓存数组

堆顶始终是最小元素,故查询是O(1)。
而进队出队,都要调整堆,是O(log(n))。

RMQ的方法

RMQ即Range Maximum(Minimum) Query,用来求某个区间内的最大值或最小值。使用线段树或稀疏表是O(log(n))级的。对于这类问题这两种方法也搞得定,但是没有单调队列快。

单调队列的舞台

由于单调队列的队头每次一定最小值,故查询为O(1)。
进队出队稍微复杂点:
进队时,将进队的元素为e,从队尾往前扫描,直到找到一个不大于e的元素d,将e放在d之后,舍弃e之后的所有元素;如果没有找到这样一个d,则将e放在队头(此时队列里只有这一个元素)。
出队时,将出队的元素为e,从队头向后扫描,直到找到一个元素f比e后进队,舍弃f之前所有的。(实际操作中,由于是按序逐个出队,所以每次只需要出队只需要比较队头)。
每个元素最多进队一次,出队一次,摊排分析下来仍然是 O(1)。
上面的话可能还是没能讲出单调队列的核心:队列并不实际存在的,实际存在的是具有单调性的子序列。对这个子序列按心中的队列进行操作,譬如在进队时丢弃的元素,虽然它不存在于这个子序列里,但是还是认为他存在于队列里。
另外,进队的顺序和出队的顺序并不一定相同,因为这个队列本身是隐含存在的,可以在进队时看成一个队列,出队时看成另一个队列,只要出队的元素在队列中就行。可以想象成一个队列只有头和身,另一个队列只有身和尾,而这身是共用的。
在OI赛场上,大多数题目为单调队列力所不能及的,取而代之的是单调队列基础上改进的斜率优化,单调栈等,因为其限制条件,故潜力不大。但需要掌握,因为有许多算法建立在其基础上。
例如斜率优化即为f[i] = min/max{f[j] + g[i] * g[j]},和单调队列尤为相似。
单调栈即为单调队列的“栈”版。
这两种复杂度也是O(n)的。
在信息学竞赛的一些应用
编辑
动态规划·单调队列的理解
做动态规划时常常会见到形如这样的转移方程:
f[x] = max or min{g(k) | b[x] <= k < x} + w[x]
(其中b[x]随x单调不降,即b[1]<=b[2]<=b[3]<=…<=b[n])
(g[k]表示一个和k或f[k]有关的函数,w[x]表示一个和x有关的函数)
这个方程怎样求解呢?我们注意到这样一个性质:如果存在两个数j, k,使得j <= k,而且g(k) <= g(j),则决策j是毫无用处的。因为根据b[x]单调的特性,如果j可以作为合法决策,那么k一定可以作为合法决策,并且k是一个比j要优的决策。因为k比j要优,(注意:在这个经典模型中,“优”是绝对的,是与当前正在计算的状态无关的),所以,如果把待决策表中的决策按照k排序的话,则g(k)必然是不降的。在此例中决策表即f[x].
这样,就引导我们使用一个单调队列来维护决策表。对于每一个状态f(x)来说,计算过程分为以下几步:
1、 队首元素出队,直到队首元素在给定的范围中。
2、 此时,队首元素就是状态f(x)的最优决策,
3、 计算g(x),并将其插入到单调队列的尾部,同时维持队列的单调性(不断地出队,直到队列单调为止)。
重复上述步骤直到所有的函数值均被计算出来。不难看出这样的算法均摊时间复杂度是O(1)的。因此求解f(x)的时间复杂度从O(n^2)降到了O(n)。
单调队列指一个队列中的所有的数符合单调性(单调增或单调减),在信息学竞赛的一些题目上应用,会减少时间复杂度
例题1:广告印刷(ad.pas/c/cpp)
【问题描述】
afy决定给TOJ印刷广告,广告牌是刷在城市的建筑物上的,城市里有紧靠着的N个建筑。afy决定在上面找一块尽可能大的矩形放置广告牌。我们假设每个建筑物都有一个高度,从左到右给出每个建筑物的高度H1,H2…HN,且0

vartemp,ans:int64;
n,p,q,i,j:longint;
a:array[0..400000]of longint;
b,r,l:array[0..400000]of longint;
begin
fillchar(b,sizeof(b),0);
readln(n);
for i:=1 to n do read(a[i]);
p:=1;
q:=0;
for i:=1 to n+1 do begin
while (p<=q) and (a[i]<a[b[q]]) do begin
r[b[q]]:=i;
dec(q);
end;
inc(q);
b[q]:=i;
end;

fillchar(b,sizeof(b),0);
p:=1;
q:=0;
for i:=n downto 0 do begin
while (p<=q) and (a[i]<a[b[q]]) do begin
l[b[q]]:=i;
dec(q);
end;
inc(q);
b[q]:=i;
end;
for i:=1 to n do begin
temp:=(r[i]-l[i]-1)*a[i];
if temp>ans then ans:=temp;
end;

writeln(ans);
end.

测试数据
样例输入1【ad.in】
20
12 8 8 30 40 32 19 22 12 32 30 45 15 1937 5 5 6 26 35
样例输出1 【ad.out】
144
样例输入2 【ad.in】
56
3000 2000 180 190 2890 2900 3120 450560 500 300 2100 2300 480 840 880 890 350 550 450 760 960 860 250 260 1050 11301140 2140 2045 2065 3075 3155 3255 3470 3490 3240 920 930 900 930 980 890 740760 770 825 845 855 950 1980 880 680 690 2380 2390
样例输出2【ad.out】
21080

poj_2823_单调队列
【问题描述】
有N个数,每次从左到右选取M个数,第一行选取每个区间中的最小值输出,第二行选取最大值并输出。
【解题思路】
这个是典型的固定k区间的单调队列。套用的本质思想是,如求最小值: 考虑这样的一个问题,在某个区间当中如果存在某两个元素A,B,满足A的下标小于B的下标,A的值大于B的值,那么A这个数就可以删掉,不再考虑。求最大值反之。
具体的操作是:从加入第k个数开始,每插入做一次队列单调性更新:
删队尾【单调性】,入队,删队首【下标范围k以内】,输出队首【即最值】。
需要注意的是,这里用纯单调队列会超时,把删队尾的操作改成二分的话就过了。

#include<stdio.h>
#include<stdlib.h>
#defineN1000001
typedefstruct{
intvalue;
intindex;
}QUE;
QUEmin_que[N],max_que[N];
intn,k,num[N],front,rear;
intdelete_rear_inc(intf,intr,intd){
intmid;
while(f<=r){
mid=(f+r)/2;
if(min_que[mid].value==d)
returnmid;
if(min_que[mid].value>d)
r=mid-1;
else
f=mid+1;
}
returnf;}
intdelete_rear_dec(intf,intr,intd){
intmid;
while(f<=r){
mid=(f+r)/2;
if(max_que[mid].value==d)
returnmid;
if(max_que[mid].value>d)
f=mid+1;
else
r=mid-1;
}
returnf;}

main(){
inti;
//
while(
scanf("%d%d",&n,&k)!=
EOF){
scanf("%d%d",&n,&k);
for(i=1;i<=n;i++)
scanf("%d",&num[i]);
//单调队列求最小——维持k区间的递增队列
min_que[1].
value=num[1];
min_que[1].index=1;
front=1;
rear=1;
//1->k
for(i=2;i<=k;i++){
//删队尾,入队
rear=delete_rear_inc(front,rear,num[i]);
min_que[rear].value=num[i];
min_que[rear].index=i;
}

printf("%d",min_que[1].value);
//队首即为第一个滑动窗口的最小值
//k+1->n
for(i=k+1;i<=n;i++){
//删队尾,入队,维持区间范围
rear=delete_rear_inc(front,rear,num[i]);
min_que[rear].value=num[i];
min_que[rear].index=i;
//删队首,维持区间范围
if(i-min_que[front].index>=k)
front++;
//队首为第i-k+1个滑动窗口的最小值

printf("%d",min_que[front].
value);
}
printf("\n");
//单调队列求最大——维持k区间的递减队列
max_que[1].value=num[1];
max_que[1].index=1;
front=1;
rear=1;
//1->k
for(i=2;i<=k;i++){
//删队尾,入队
rear=delete_rear_dec(front,rear,num[i]);
max_que[rear].
value=num[i];
max_que[rear].index=i;
}

printf("%d",max_que[1].value);
//队首即为第一个滑动窗口的最大值
//k+1->n
for(i=k+1;i<=n;i++){
//删队尾,入队,维持区间范围
rear=delete_rear_dec(front,rear,num[i]);
max_que[rear].value=num[i];
max_que[rear].index=i;
//删队首,维持区间范围
if(i-max_que[front].index>=k)
front++;
//队首为第i-k+1个滑动窗口的最大值

printf("%d",max_que[front].
value);
}
printf("\n");
//}

//system("pause");
return0;}

相关文章:

  • 二分图大讲堂——彻底搞定最大匹配数(最小覆盖数)、最大独立数、最小路径覆盖、带权最优匹配
  • 有向强连通和网络流大讲堂——史无前例求解最大流(最小割)、最小费用最大流
  • [2016.7 Day.4] T1 游戏 [正解:二分图 偏解:奇葩贪心+模拟?(不知如何称呼不过居然比std还快)]
  • [2016.7 day.5] T2
  • [2016.7 test.5] T1
  • [hdu 4552] 怪盗基德的挑战书
  • 从头到尾彻底理解KMP
  • KMP模板
  • GNU g++常用编译选项用法
  • [hdu 1711] Number Sequence [kmp]
  • [poj 3461]Oulipo[kmp]
  • strcpy和strncpy用法和区别
  • [POJ 2406]Power Strings[KMP]
  • markdown语法
  • BestCoder 2nd Anniversary #1001-oracle
  • [译] 怎样写一个基础的编译器
  • 【Amaple教程】5. 插件
  • 03Go 类型总结
  • Javascript弹出层-初探
  • Linux链接文件
  • Markdown 语法简单说明
  • Spring Cloud Alibaba迁移指南(一):一行代码从 Hystrix 迁移到 Sentinel
  • swift基础之_对象 实例方法 对象方法。
  • Vue ES6 Jade Scss Webpack Gulp
  • Vue官网教程学习过程中值得记录的一些事情
  • 基于组件的设计工作流与界面抽象
  • 解决jsp引用其他项目时出现的 cannot be resolved to a type错误
  • 开源地图数据可视化库——mapnik
  • 前端学习笔记之原型——一张图说明`prototype`和`__proto__`的区别
  • postgresql行列转换函数
  • 仓管云——企业云erp功能有哪些?
  • 如何用纯 CSS 创作一个货车 loader
  • ​MySQL主从复制一致性检测
  • ​软考-高级-系统架构设计师教程(清华第2版)【第15章 面向服务架构设计理论与实践(P527~554)-思维导图】​
  • ​一帧图像的Android之旅 :应用的首个绘制请求
  • #162 (Div. 2)
  • #android不同版本废弃api,新api。
  • (2)MFC+openGL单文档框架glFrame
  • (pytorch进阶之路)CLIP模型 实现图像多模态检索任务
  • (ZT) 理解系统底层的概念是多么重要(by趋势科技邹飞)
  • (四)库存超卖案例实战——优化redis分布式锁
  • (转)Java socket中关闭IO流后,发生什么事?(以关闭输出流为例) .
  • (转)mysql使用Navicat 导出和导入数据库
  • * CIL library *(* CIL module *) : error LNK2005: _DllMain@12 already defined in mfcs120u.lib(dllmodu
  • .helper勒索病毒的最新威胁:如何恢复您的数据?
  • .locked1、locked勒索病毒解密方法|勒索病毒解决|勒索病毒恢复|数据库修复
  • .net mvc actionresult 返回字符串_.NET架构师知识普及
  • .net mvc部分视图
  • .net 打包工具_pyinstaller打包的exe太大?你需要站在巨人的肩膀上-VC++才是王道
  • .Net 应用中使用dot trace进行性能诊断
  • .NET4.0并行计算技术基础(1)
  • .NET开发不可不知、不可不用的辅助类(三)(报表导出---终结版)
  • .NET设计模式(11):组合模式(Composite Pattern)
  • .net图片验证码生成、点击刷新及验证输入是否正确
  • /etc/sudoers (root权限管理)