RAM区间最值

RMQ (Range Minimum/Maximum Query)问题是指:对于长度为n的数列A,回答若干询问RMQ(A,i,j)(i,j<=n),返回数列A中下标在i,j里的最小(大)值,也就是说,RMQ问题是指求区间最值的问题。

简介

  主要方法及复杂度如下:

  1、朴素(即搜索),O(n)-O(qn) online。

  2、线段树,O(n)-O(qlogn) online。

  3、ST(实质是动态规划),O(nlogn)-O(1) online。

  ST算法(Sparse Table),以求最大值为例,设d[i,j]表示[i,i+2^j-1]这个区间内的最大值,那么在询问到[a,b]区间的最大值时答案就是max(d[a,k], d[b-2^k+1,k]),其中k是满足2^k<=b-a+1(即长度)的最大的k,即k=[ln(b-a+1)/ln(2)]。

  d的求法可以用动态规划,d[i, j]=max(d[i, j-1],d[i+2^(j-1), j-1])。

  4、RMQ标准算法:先规约成LCA(Lowest Common Ancestor),再规约成约束RMQ,O(n)-O(1) online。

  首先根据原数列,建立笛卡尔树,从而将问题在线性时间内规约为LCA问题。LCA问题可以在线性时间内规约为约束RMQ,也就是数列中任意两个相邻的数的差都是+1或-1的RMQ问题。约束RMQ有O(n)-O(1)的在线解法,故整个算法的时间复杂度为O(n)-O(1)。

ST算法

  来看一下ST算法是怎么实现的(以最大值为例):

  首先是预处理,用一个DP解决。设a是要求区间最值的数列,f[i,j]表示从第i个数起连续2^j个数中的最大值。例如数列3 2 4 5 6 8 1 2 9 7 ,f[1,0]表示第1个数起,长度为2^0=1的最大值,其实就是3这个数。f[1,2]=5,f[1,3]=8,f[2,0]=2,f[2,1]=4……从这里可以看出f[i,0]其实就等于a。这样,DP的状态、初值都已经有了,剩下的就是状态转移方程。我们把f[i,j](j≥1)平均分成两段(因为j≥1时,f[i,j]一定是偶数个数字),从i到i+2^(j-1)-1为一段,i+2^(j-1)到i+2^j-1为一段(长度都为2^(j-1))。用上例说明,当i=1,j=3时就是3,2,4,5 和6,8,1,2这两段。f就是这两段的最大值中的最大值。于是我们得到了动规方程F[i,j]=max(F[i,j-1],F[i+2^(j-1),j-1])。

  接下来是得出最值,也许你想不到计算出f有什么用处,一般要想想计算max还是要O(logn),甚至O(n)。但有一个很好的办法,做到了O(1)。还是分开来。如在上例中我们要求区间[2,8]的最大值,就要把它分成[2,5]和[5,8]两个区间,因为这两个区间的最大值我们可以直接由f[2,2]和f[5,2]得到。扩展到一般情况,就是把区间[l,r]分成两个长度为2^n的区间(保证有f对应)。直接给出表达式:

  k:=trunc(ln(r-l+1)/ln(2));

  ans:=max(F[l,k],F[r-2^k+1,k]);

  这样就计算了从l开始,长度为2^k的区间和从r-2^k+1开始长度为2^k的区间的最大值(表达式比较烦琐,细节问题如加1减1需要仔细考虑),二者中的较大者就是整个区间[l,r]上的最大值。

标准算法

建立笛卡尔树

  数组A[0,N-1]的笛卡尔树C是这样一棵二叉树:当N=0,它是一棵空树,否则它的根节点是A中的一个最小元素A[i](并以这个最小元素的下标i标记),而左右子树分别是A[0,i-1]和A[i+1,N-1]的一棵笛卡尔树。注意如果A中有相等的元素,则A的笛卡尔树不一定唯一,但在这里我们限定所用的最小元素为在数组中最先出现者,在此限制下笛卡尔树是唯一的。

  容易看出,数组A在闭区间[l,r]上的最小值等同于笛卡尔树C中下标为l和r的两个顶点的最近公共祖先(LCA)的值。由此,RMQ问题可以转化为LCA问题。下面说明如何在O(n)时间内实现这一转化。

  我们将要将A的元素依次插入笛卡尔树C。每次插入都可能使树的形态发生变化。为了在O(N)的时间内完成整个插入过程,考虑C的右链,即根结点、根结点的右儿子、根结点的右儿子的右儿子……组成的链。注意这些元素的下标和值都是递增的。下标最大,即将要插入的元素A[i]一定是新树右链的最后一个元素。原来的右链中,值比A[i]大的元素在新树中不再属于右链,这些元素组成的链成为A[i]的左子树的右链;原来右链中的其它元素加上A[i]组成了新的右链。初看起来,寻找分界点的最佳方法是O(logN)时间的二分查找;但是对于整个过程来说,O(NlogN)的时间复杂度不是最优的。关键在于一旦一个元素比A[i]大,它就从右链中被永久地移除了。如果按照从后到前的顺序判断一个元素是否大于A[i],则每次插入的时间复杂度为O(k+1),k为本次插入中移除的右链元素个数。因为每个元素最多进出右链各一次,所以整个过程的时间复杂度为O(N)。

  用一个栈结构维护右链元素的下标,上述过程可以很容易地实现。(见下面代码部分)

转化为约束RMQ

  为了将LCA问题转化为约束RMQ,我们注意到任意树中两个结点u和v的LCA就是在一次从树根开始的深度优先搜索中,在u和v之间(包括回溯时)到达的结点中层数最小的一个。为了利用这一事实,我们建立三个数组:

  E[1,2*N-1]:在一次深度优先搜索(恰好是树的一次欧拉环游)中每一步到达的结点。

  L[1,2*N-1]:E中对应结点在树中的层数。

  H[1,N]:每个结点在E中某一次出现的下标(不妨设为第一次)。

  则对任意u和v,不妨设H[u]≤H[v](否则交换u和v),只要在L中找到[H[u],H[v]]中最小值的下标i,则E[i]就是u和v的LCA。注意到L满足约束RMQ的条件(相邻元素差的绝对值为1),这说明原来的LCA问题已经被转化为约束RMQ。转化过程显然能在O(N)时间内完成。

约束RMQ的解法

  现在仍旧用A[0,N-1]表示问题中的数列,这里有|A[i]-A[i-1]|=1(i=1,2,...,N-1)成立。

  将A分解为长度为l=[(log N)/2]的块。设A‘[i]为第i块中的最小值,B[i]为该最小值的位置。A‘[i]和B[i]的长度均为N/l, 所以用ST算法处理A‘数组的时空复杂度均为O(N/l*log(N/l))=O(N/logN*(logN-logl))=O(N)。预处理之后,对任意多连续的块进行的查询都能在O(1)时间内实现。余下的问题是如何进行块内查询。

  注意到对任意一块中的块内查询的结果有影响的唯一因素是块内每相邻两个元素间的“升降关系”构成的序列。因为每两个元素之间的关系只有两种(“+1”、“-1”),而块的长度又只有l=[(log N)/2],所以本质不同的块最多有2^I=O(sqrt N)种。对每种块中所有可能的块内查询预处理出答案的时空复杂度是O(sqrt N*l^2)=O(N)(这里的O(N)表示不超过线性时间)。预处理出所有块的“类型”,并用二进制数存储的时间复杂度是O(N)。

  此后,每次查询可以分为两种情况:

  1、块内查询,答案已经被预处理出,只要在数组中找到它即可。

  2、块间查询,可以分解为2个块内查询,和一个A‘上的RMQ,三者的时间复杂度都是O(1)。

  综上,我们给出了一个预处理时间为O(n),查询时间为O(1)的在线RMQ算法。

代码

ST算法,Pascal,程序片段

  Read(n, q);

  for i := 1 to n do

  Read(d[i, 0]);

  for j := 1 to Trunc(Ln(n) / Ln(2)) do

  for i := 1 to n - 1 shl j + 1 do

  d[i,j] := Max(d[i,j-1], d[i+1 shl (j-1),j-1]);

  for i := 1 to q do

  begin

  Read(a, b);

  k := Trunc(Ln(b - a + 1) / Ln(2));

  rmq := Max(d[a, k], d[b - 1 shl k + 1, k]);

  Writeln(rmq);

  end;

  RMQ(Range Minimum/Maximum Query)问题是求区间最值问题。你当然可以写个O(n)的(怎么写都可以),但是万一要询问最值1000000遍,估计你就要挂了。这时候你可以放心地写一个线段树(前提是不写错)O(logn)的复杂度应该不会挂。但是,这里有更牛的算法,就是ST算法,它可以做到O(nlogn)的预处理,O(1)地回答每个询问。

ST算法,Pascal,完整过程

  var p,n,m,z:longint;

  aa,data:array[0..100001]of longint;

  procedure myread;

  var i:longint;

  begin

  read(n,m);

  for i:=1 to n do read(aa[i]);

  z:=trunc(sqrt(n));data[1]:=maxlongint;p:=1;

  for i:=1 to n do

  begin

  if aa[i]<data[p] then data[p]:=aa[i];

  if i mod z=0 then begin inc(p);data[p]:=maxlongint;end;

  end;

  end;

  procedure main;

  var tt1,tt2,a,b,i,j,t1,t2,min:longint;

  begin

  for i:=1 to m do

  begin

  read(a,b);

  t1:=a div z +1;

  t2:=b div z +1;

  tt1:=a mod z;

  tt2:=b mod z;

  min:=maxlongint;

  if t1=t2 then

  begin

  for j:=a to b do

  if min>aa[j] then min:=aa[j];

  write(min,‘ ‘);

  continue;

  end;

  for j:=((t1-1)*z+tt1) to t1*z do

  if min>aa[j] then min:=aa[j];

  for j:=((t2-1)*z) to ((t2-1)*z+tt2) do

  if min>aa[j] then min:=aa[j];

  for j:=t1+1 to t2-1 do

  if min>data[j] then min:=data[j];

  write(min,‘ ‘);

  end;

  end;

  begin

  myread;main;

  end.

ST算法,C++

  #include <iostream>

  #include <cstdio>

  #include <cmath>

  #define max(a,b) (a>b?a:b)

  #define min(a,b) (a<b?a:b)

  #define MN 50005

  using namespace std;

  int mi[MN][17],mx[MN][17],w[MN];

  int n,q;

  void rmqinit()

  {

  int i,j,m;

  for(i=1;i<=n;i++){mi[i][0]=mx[i][0]=w[i];}

  m=floor(log((double)n)/log(2.0));

  for(i=1;i<=m;i++)

  {

  for(j=n;j>=1;j--)

  {

  mx[j][i]=mx[j][i-1];

  if(j+(1<<(i-1))<=n)mx[j][i]=max(mx[j][i],mx[j+(1<<(i-1))][i-1]);

  mi[j][i]=mi[j][i-1];

  if(j+(1<<(i-1)<=n))mi[j][i]=min(mi[j][i],mi[j+(1<<(i-1))][i-1]);

  }

  }

  }

  int rmqmin(int l,int r)

  {

  int m=floor(log((double)(r-l+1))/log(2.0));

  return min(mi[l][m],mi[r-(1<<m)+1][m]);

  }

  int rmqmax(int l,int r)

  {

  int m=floor(log((double)(r-l+1))/log(2.0));

  return max(mx[l][m],mx[r-(1<<m)+1][m]);

  }

  int main()

  {

  cin>>n>>q;

  for(int i=1;i<=n;i++)scanf("%d",&w[i]);//cin>>w[i];

  rmqinit();

  int l,r;

  for(int i=1;i<=q;i++)

  {

  scanf("%d%d",&l,&r);//cin>>l>>r;

  printf("%d %d\n",rmqmax(l,r),rmqmin(l,r));

  //cout<<rmqmax(l,r)<<" "<<rmqmin(l,r)<<endl;

  }

  while(cin>>n)

  return 0;

  }

建立笛卡尔树,C++

  void computeTree(int A[MAXN], int N, int T[MAXN])

  //T[i]储存每个结点的父结点(左右子树是无所谓的)

  {

  int st[MAXN], i, k, top = -1;

  //从空栈开始

  //第i步,我们将A[i]插入栈中

  for (i = 0; i < N; i++)

  {

  //找到第一个小于等于A[i]的元素

  k = top;

  while (k >= 0 && A[st[k]] > A[i])

  k--;

  //如上述,更改树的结构

  if (k != -1)

  T[i] = st[k];

  if (k < top)

  T[st[k + 1]] = i;

  //将A[i]插入栈中,并移除所有更大的元素

  st[++k] = i;

  top = k;

  }

  //栈中的第一个元素就是树根,没有父节点

  T[st[0]] = -1;

  }

时间: 2024-10-12 06:51:17

RAM区间最值的相关文章

树状数组维护区间最值

在区间求和时,我们只需求出 [1, r],[1,l?1],利用前缀和的可减性,得到区间 [l,r] 的和. 但区间最值不满足这个性质. 我们可以把区间 [l,r] 拆分成若干个子区间,再合并得到答案. 画图可知,max_i需要的 max 只有 max_{i-2^0}, max_{i-2^1}, max_{i-2^2} ... max_{i-lowbit(i)+1}. 修改 void change(int r) { c[r] = a[r]; for(int i = 1; i < lowbit(r)

树状数组求区间最值

树状数组求区间最值 树状数组(Binary Index Tree)利用二进制的一些性质巧妙的划分区间,是一种编程,时间和空间上都十分理想的求区间和的算法,同样我们可以利用树状数组优美的区间划分方法来求一个序列的最值 约定以 num[]  表示原数组, 以 idx[] 表示索引数组, Lowbit(x)=x&(-x) 树状数组求和时通过构造数组 idx[] 使 idx[k]=sum(num[tk]), tk [k-Lowbit(k)+1,k], 使用同样的方法构造最值索引数组: 以最大值为例, 先

算法模板——线段树3(区间覆盖值+区间求和)

实现功能——1:区间覆盖值:2:区间求和 相比直接的区间加,这个要注重顺序,因为操作有顺序之分.所以这里面的tag应该有个pushup操作(本程序中的ext) 1 var 2 i,j,k,l,m,n,a1,a2,a3,a4:longint; 3 a,b,d:array[0..100000] of longint; 4 function max(x,y:longint):longint;inline; 5 begin 6 if x>y then max:=x else max:=y; 7 end;

算法模板——线段树4(区间加+区间乘+区间覆盖值+区间求和)

实现功能——1:区间加法 2:区间乘法 3:区间覆盖值 4:区间求和 这是个四种常见线段树功能的集合版哦...么么哒(其实只要协调好三种tag的关系并不算太难——前提是想明白了线段树的工作模式) 代码长度几经修改后也大为缩水 还有!!!——通过BZOJ1798反复的尝试,我的出来一个重要结论——尽量减少pushup操作的不必要使用次数,对于程序提速有明显的效果!!! 1 type vet=record 2 a0,a1:longint; 3 end; 4 var 5 i,j,k,l,m,n,a1,

hdu 5443 (2015长春网赛G题 求区间最值)

求区间最值,数据范围也很小,因为只会线段树,所以套了线段树模板=.= Sample Input3110011 151 2 3 4 551 21 32 43 43 531 999999 141 11 22 33 3 Sample Output1002344519999999999991 1 # include <iostream> 2 # include <cstdio> 3 # include <cstring> 4 # include <algorithm>

LightOJ Array Queries 1082【线段树求区间最值】

1082 - Array Queries PDF (English) Statistics Forum Time Limit: 3 second(s) Memory Limit: 64 MB Given an array with N elements, indexed from 1 to N. Now you will be given some queries in the form I J, your task is to find the minimum value from index

POJ - 3264 Balanced Lineup (RMQ问题求区间最值)

RMQ (Range Minimum/Maximum Query)问题是指:对于长度为n的数列A,回答若干询问RMQ(A,i,j)(i,j<=n),返回数列A中下标在i,j里的最小(大)值,也就是说,RMQ问题是指求区间最值的问题. Balanced Lineup Time Limit: 5000MS   Memory Limit: 65536KB   64bit IO Format: %I64d & %I64u Submit Status Description For the daily

poj 2763 树链剖分(单点更新,区间求值)

http://poj.org/problem?id=2763 Description After their royal wedding, Jiajia and Wind hid away in XX Village, to enjoy their ordinary happy life. People in XX Village lived in beautiful huts. There are some pairs of huts connected by bidirectional ro

【RMQ】 区间最值查询详解

1. 概述 RMQ(Range Minimum/Maximum Query),即区间最值查询,是指这样一个问题:对于长度为n的数列A,回答若干询问RMQ(A,i,j)(i,j<=n),返回数列A中下标在i,j之间的最小/大值.这两个问题是在实际应用中经常遇到的问题,下面介绍一下解决这两种问题的比较高效的算法.当然,该问题也可以用线段树(也叫区间树)解决,算法复杂度为:O(N)~O(logN),这里我们暂不介绍. 2.RMQ算法 对于该问题,最容易想到的解决方案是遍历,复杂度是O(n).但当数据量