单调队列在信息竞赛的研究与应用.docx

上传人:b****5 文档编号:2799467 上传时间:2022-11-15 格式:DOCX 页数:27 大小:56.66KB
下载 相关 举报
单调队列在信息竞赛的研究与应用.docx_第1页
第1页 / 共27页
单调队列在信息竞赛的研究与应用.docx_第2页
第2页 / 共27页
单调队列在信息竞赛的研究与应用.docx_第3页
第3页 / 共27页
单调队列在信息竞赛的研究与应用.docx_第4页
第4页 / 共27页
单调队列在信息竞赛的研究与应用.docx_第5页
第5页 / 共27页
点击查看更多>>
下载资源
资源描述

单调队列在信息竞赛的研究与应用.docx

《单调队列在信息竞赛的研究与应用.docx》由会员分享,可在线阅读,更多相关《单调队列在信息竞赛的研究与应用.docx(27页珍藏版)》请在冰豆网上搜索。

单调队列在信息竞赛的研究与应用.docx

单调队列在信息竞赛的研究与应用

单调队列在信息学竞赛的应用和初步研究

百科名片

单调队列是个什么东西呢,其实没什么滑头,简单望文生义就可以了,单调的队列是也。

譬如最小队列{3,4,5,7},队列内为非降序,队列头肯定是当前最小的(注意队尾不一定是最大值)。

单调队列的操作

  不妨用一个问题来说明单调队列的作用和操作:

  不断地向buffer里读入元素,也不时地去掉最老的元素,不定期的询问当前buffer里的最小的元素。

  最StraightForward的方法:

普通队列实现buffer。

  进队出队都是O

(1),一次查询需要遍历当前队列的所有元素,故O(n)。

用堆实现buffer。

  堆顶始终是最小元素,故查询是O

(1)。

  而进队出队,都要调整堆,是O(logn)。

RMQ的方法

  使用SegmentTree或SparseTable,线段树没写过(树状数组用过),是logn级的;稀疏表不懂,只知道是logn级的。

对于这类问题这两种方法也搞得定,但是没有本文主人公来得快。

单调队列的舞台

  由于单调队列的队头每次一定最小值,故查询为O

(1)。

  进队出队稍微复杂点:

  进队时,将进队的元素为e,从队尾往前扫描,直到找到一个不大于e的元素d,将e放在d之后,舍弃e之后的所有元素;如果没有找到这样一个d,则将e放在队头(此时队列里只有这一个元素)。

  出队时,将出队的元素为e,从队头向后扫描,直到找到一个元素f比e后进队,舍弃f之前所有的。

(实际操作中,由于是按序逐个出队,所以每次只需要出队只需要比较队头)。

  每个元素最多进队一次,出队一次,摊排分析下来仍然是O

(1)。

  上面的话可能还是没能讲出单调队列的核心:

队列并不实际存在的,实际存在的是具有单调性的子序列。

对这个子序列按心中的队列进行操作,譬如在进队时丢弃的元素,虽然它不存在于这个子序列里,但是还是认为他存在于队列里。

  poj2823是一个典型的单调队列题,不过因为题目要求中大量的输入输出的使得时间要求竟然卡scanf和printf的字符串解析(直接实现要5000ms),害得我极其猥琐的手动输入输出解析(800+ms),真是不爽。

  另外进队的顺序和出队的顺序并不一定相同,因为这个队列本身是隐含存在的,可以在进队时看成一个队列,出队时看成另一个队列,只要出队的元素在队列中就行。

可以想象成一个队列只有头和身,另一个队列只有身和尾,而这身是共用的。

在信息学竞赛的一些应用

  先介绍单调队列

  做动态规划时常常会见到形如这样的转移方程:

  f[x]=maxormin{g(k)|b[x]<=k

  (其中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排序的话,则g(k)必然是不降的。

  这样,就引导我们使用一个单调队列来维护决策表。

对于每一个状态f(x)来说,计算过程分为以下几步:

  1、队首元素出队,直到队首元素在给定的范围中。

  2、此时,队首元素就是状态f(x)的最优决策,

  3、计算g(x),并将其插入到单调队列的尾部,同时维持队列的单调性(不断地出队,直到队列单调为止)。

  重复上述步骤直到所有的函数值均被计算出来。

不难看出这样的算法均摊时间复杂度是O

(1)的。

因此求解f(x)的时间复杂度从O(n^2)降到了O(n)。

  单调队列指一个队列中的所有的数符合单调性(单调增或单调减),在信息学竞赛的一些题目上应用,会减少时间复杂度

编辑本段例题:

广告印刷(ad.pas/c/cpp)

  【问题描述】

  最近,afy决定给TOJ印刷广告,广告牌是刷在城市的建筑物上的,城市里有紧靠着的N个建筑。

afy决定在上面找一块尽可能大的矩形放置广告牌。

我们假设每个建筑物都有一个高度,从左到右给出每个建筑物的高度H1,H2…HN,且0

要求输出广告牌的最大面积。

  【输入文件】

  中的第一行是一个数n(n<=400,000)

  第二行是n个数,分别表示每个建筑物高度H1,H2…HN,且0

  【输出文件】

  输出文件ad.out中一共有一行,表示广告牌的最大面积。

  【输入样例】

  6

  584484

  【输出样例】

  24

  【解释】各个测试点一秒,

  但就这道题来说,n<=400,000,我们如果用枚举不会过全部数据,我们应设计出o(n)的算法来解决,这是单调队列就可以派上用场了。

  具体做法是先正着扫一遍,再倒着扫一遍,找到每一个数的右极限与左极限,最后找出最大值。

代码:

  programbensen;

  var

  temp,ans:

int64;

  n,p,q,i,j:

longint;

  a:

array[0..400000]oflongint;

  b,r,l:

array[0..400000]oflongint;

  begin

  readln(n);

  fori:

=1tondo

  read(a[i]);

  p:

=1;q:

=0;

  fori:

=1ton+1do

  begin

  while(p<=q)and(a[i]

  begin

  r[b[q]]:

=i;

  dec(q);

  end;

  inc(q);b[q]:

=i;

  end;

  fillchar(b,sizeof(b),0);

  p:

=1;q:

=0;

  fori:

=ndownto0do

  begin

  while(p<=q)and(a[i]

  begin

  l[b[q]]:

=i;

  dec(q);

  end;

  inc(q);b[q]:

=i;

  end;

  fori:

=1tondo

  begin

  temp:

=(r[i]-l[i]-1)*a[i];

  iftemp>ansthenans:

=temp;

  end;

  writeln(ans);

  end.

单调队列及其应用

关键字

队列,合并果子,窗户,广告印刷,最长XX子序列,志愿者选拔,动态规划,烽火传递

正文

单调队列,望文生义,就是指队列中的元素是单调的。

如:

{a1,a2,a3,a4……an}满足a1<=a2<=a3……<=an,a序列便是单调递增序列。

同理递减队列也是存在的。

单调队列的出现可以简化问题,队首元素便是最大(小)值,这样,选取最大(小)值的复杂度便为o

(1),由于队列的性质,每个元素入队一次,出队一次,维护队列的复杂度均摊下来便是o

(1)。

如何维护单调队列呢,以单调递增序列为例:

1、如果队列的长度一定,先判断队首元素是否在规定范围内,如果超范围则增长对首。

2、每次加入元素时和队尾比较,如果当前元素小于队尾且队列非空,则减小尾指针,队尾元素依次出队,直到满足队列的调性为止

要特别注意头指针和尾指针的应用。

下面介绍单调队列的具体应用:

一、单调队列的直接应用

1.合并果子

【问题描述】 

在一个果园里,多多已经将所有的果子打了下来,而且按果子的不同种类分成了不同的堆。

多多决定把所有的果子合成一堆。

每一次合并,多多可以把两堆果子合并到一起,消耗的体力等于两堆果子的重量之和。

可以看出,所有的果子经过n-1次合并之后,就只剩下一堆了。

多多在合并果子时总共消耗的体力等于每次合并所耗体力之和。

 

因为还要花大力气把这些果子搬回家,所以多多在合并果子时要尽可能地节省体力。

假定每个果子重量都为1,并且已知果子的种类数和每种果子的数目,你的任务是设计出合并的次序方案,使多多耗费的体力最少,并输出这个最小的体力耗费值。

 

例如有3种果子,数目依次为1,2,9。

可以先将 1、2堆合并,新堆数目为3,耗费体力为3。

接着,将新堆与原先的第三堆合并,又得到新的堆,数目为12,耗费体力为 12。

所以多多总共耗费体力=3+12=15。

可以证明15为最小的体力耗费值。

 

【输入文件】 

输入文件fruit.in包括两行,第一行是一个整数n(1<=n<=10000),表示果子的种类数。

第二行包含n个整数,用空格分隔,第i个整数ai(1<=ai<=20000)是第i种果子的数目。

 

【输出文件】 

输出文件fruit.out包括一行,这一行只包含一个整数,也就是最小的体力耗费值。

输入数据保证这个值小于231。

 

【样例输入】 

129 

【样例输出】 

15 

【数据规模】 

对于30%的数据,保证有n<=1000; 

对于50%的数据,保证有n<=5000; 

对于全部的数据,保证有n<=10000。

 

分析:

这个题目非常的经典,发放也很多,可以采用快排或者堆,其思想都是选取当前最小的两个堆进行合并。

复杂度均为O(nlogn),如果用有序队列维护,时间复杂度为O(n)。

每次选取进行合并的两堆,不是最先给定的堆,就是合并最初堆若干次后得到的新堆,所以需要维护两个单调递增队列,一个队列存最初给定的堆的值

(1),一个存合并后得到的新值

(2)。

每次选择时有三种状态:

1、  选取队一的队首两个  2、选取队2的的队首两个 3、选取二者队首各一个

只需对每个队列的指针做相应的更改。

特别注意初始化。

这道题很好的运用了题目中决策的单调性,对初始对经行排序,保证了其单调性。

而对于新产生的堆来说,一旦有新元素加入其中,则新元素一定大于原有元素。

(很显然,由于队列1的单调性)。

也就是说,队列的单调性是自然而然的。

是不需要维护的。

要善于观察分析,才能发现。

 

Code

programliukee;

var

  a,b:

array[1..100000]oflongint;

  h1,h2,t2,temp,n,i,ans:

longint;

 

functionmin(a,b,c:

longint):

longint;

begin

  ifa

=a

         elsemin:

=b;

  ifc

=c;

end;

 

proceduresort(l,r:

longint);

var

  i,j,mid,temp:

longint;

begin

  i:

=l;

  j:

=r;

  mid:

=a[l+random(r-l)];//随机化排序

  repeat

    whilea[i]

    whilea[j]>middodec(j);

    ifi<=jthen

    begin

      temp:

=a[i];

      a[i]:

=a[j];

      a[j]:

=temp;

      inc(i);

      dec(j);

    end;

  untili>j;

  ifi

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 工程科技 > 能源化工

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1