【算法学习】区间最值问题(rmq)的st算法

RMQ(Range Minimum/Maximum Query)问题是求区间最值问题。你当然可以写个O(n)的(怎么写都可以吧=_=),但是万一要询问最值1000000遍,估计你就要挂了。这时候你 可以放心地写一个线段树(前提是不写错)应该不会挂。但是,这里有更简单的算法,就是ST算法,它可以做到O(nlogn)的预处理,O(1)地回答每个 询问。
来看一下ST算法是怎么实现的(以最大值为例):
首先是预处理,用一个DP解决。设a[i]是要求区间最值的数列,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[i]。这样,Dp的状态、初值都已经有了,剩下的就是状态转移方程。我们把f[i,j]平均分成两段(因为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[i,j]就是这两段的最大值中的最大值。于是我们得到了动规方程F[i,j]=max(F[i,j- 1],F[i+2^(j-1),j-1]).
接下来是得出最值,一个很好的办法,做到了O(1)。还是分开 来。如在上例中我们要求区间[2,8]的最大值,就要把它分成[2,5]和[5,8]两个区间,因为这两个区间的最大值我们可以直接由f[2,2]和 f[5,2]得到。扩展到一般情况,就是把区间[l,r]分成两个长度为2^k的区间(保证有f[i,j]对应)。直接给出表达式:
k:=trunc(ln(r- l+1)/ln(2));
ans:=max(F[l,k],F[r-2^k+1,k]);
(稍微解释一下:我 们求得一个k,使2^k与[x,y]内的数的个数最接近,以保证二分的两个区间包含所有区间内所有数
1 2 3 4 5 6 7 8
————————
————————
5 6 7 8 9101112
比如上图,有12个数(一条短线代表一个数),k最大为3,我们得到两 个区间[1,8]和[5,12],这两个区间虽然有重复,但并不影响我们计算最大值。最大值可由f[1,3]和f[12-2^3+1,3]得到)
这样就计算了从i开始,长度为2^t次的区间和从r-2^i+1开始长度为2^t 的区间的最大值(表达式比较烦琐,细节问题如加1减1需要仔细考虑)

特别注意最后的区间长度一定是从左右向区间中点扩展,这样就能保证答案的正确性

看一道例题

描述 Description

老管家是一个聪明能干的人。他为财主工作了整整10年,财主为了让自已账目更加清楚。要求管家每天记k次账,由于管家聪明能干,因而管家总是让财主十分满意。但是由于一些人的挑拨,财主还是对管家产生了怀疑。于是他决定用一种特别的方法来判断管家的忠诚,他把每次的账目按1,2,3…编号,然后不定时的问管家问题,问题是这样的:在a到b号账中最少的一笔是多少?为了让管家没时间作假他总是一次问多个问题。

输入格式 Input Format

输入中第一行有两个数m,n表示有m(m<=100000)笔账,n表示有n个问题,n<=100000。
第二行为m个数,分别是账目的钱数
后面n行分别是n个问题,每行有2个数字说明开始结束的账目编号。

输出格式 Output Format

输出文件中为每个问题的答案。具体查看样例。

样例

10 3
1 2 3 4 5 6 7 8 9 10
2 7
3 9
1 10

2 3 1

下面附一个标准的st代码,注意初值

program liukeke;
var
  f:array[0..100005,0..20] of longint;
  m,n,i,j,l,r,k:longint;

function min(a,b:longint):longint;
begin
  if a<b then exit(a);
  exit(b);
end;

begin
  fillchar(f,sizeof(f),127);
  readln(n,m);
  for i:=1 to n do
    read(f[i,0]);
  for j:=1 to trunc(ln(n)/ln(2))+1 do
    for i:=1 to n do
	  if (i+(1<<j)-1)<=n then
	    f[i,j]:=min(f[i,j-1],f[i+1<<(j-1),j-1]);
  for i:=1 to m do
  begin
    readln(l,r);
    if l<>r then
    begin
	k:=trunc(ln(r-l+1)/ln(2));
	write(min(f[l,k],f[r-(1<<k)+1,k]),' ')
    end
    else write(f[l,0],' ');
  end;
end.
posted @ 2011-06-21 17:48  liukee  阅读(714)  评论(1编辑  收藏  举报