RMQ 即 range maximum/minimum query
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]上的最大值。
应用:
JOJ 2660 河床
时间上明显没有直接单调队列维护最大最小快,可能是运算log上浪费了时间。
对log优化了下,直接位运算取ln2稍微快了些
#include <cstdio>
#include <cmath>
#define max(a,b) (a>b?a:b)
#define min(a,b) (a<b?a:b)
const int maxn=30005;
const double ln2=log(2.0);
int dpmax[maxn][16],dpmin[maxn][16],w[maxn];
int n;
int getln2 (int x)
{
int i;
for(i=0 ; x ; ++i,x>>=1);
return i-1;
}
void rmqinit ()
{
for (int i=0 ; i<n ; ++i)
dpmax[i][0]=dpmin[i][0]=w[i];
int m=getln2(n);
for (int i=1 ; i<=m ; ++i)
{
for (int j=n-1 ; j>=0 ; --j)
{
dpmax[j][i]=dpmax[j][i-1];//F[i,j]=max(F[i,j-1],F[i+2^(j-1),j-1])。
if(j+(1<<(i-1))<=n)dpmax[j][i]=max(dpmax[j][i],dpmax[j+(1<<(i-1))][i-1]);
dpmin[j][i]=dpmin[j][i-1];
if(j+(1<<(i-1))<=n)dpmin[j][i]=min(dpmin[j][i],dpmin[j+(1<<(i-1))][i-1]);
}
}
}
int rmqmin(int l,int r)
{
int m=getln2(r-l+1);//k:=trunc(ln(r-l+1)/ln(2));
return min(dpmin[l][m],dpmin[r-(1<<m)+1][m]);//ans:=min(F[l,k],F[r-2^k+1,k]);
}
int rmqmax(int l,int r)//O(1)
{
int m=getln2(r-l+1);
return max(dpmax[l][m],dpmax[r-(1<<m)+1][m]);
}
int k;
int main ()
{
while (~scanf("%d%d",&n,&k),(n||k))
{
//printf("%d\n",getln2(6));
for (int i=0 ; i<n ; ++i)
{
scanf("%d",w+i);
}
rmqinit();
int start=0,end=0;
int ans=0;
while(true)
{
int tmpmax=rmqmax(start,end);
int tmpmin=rmqmin(start,end);
//printf("%d %d start=%d e=%d\n",tmpmin,tmpmax,start,end);
while(tmpmax-tmpmin<=k && end<n)
{
end++;
tmpmax=rmqmax(start,end);
tmpmin=rmqmin(start,end);
}
//printf("%d %d start=%d e=%d\n",tmpmin,tmpmax,start,end);
ans=max(ans,end-start);
tmpmax=rmqmax(start,end);
tmpmin=rmqmin(start,end);
//printf("%d %d start=%d e=%d\n",tmpmin,tmpmax,start,end);
while(tmpmax-tmpmin>k && start <=end)
{
start++;
tmpmax=rmqmax(start,end);
tmpmin=rmqmin(start,end);
}
//printf("%d\n",ans);
if(start+ans>n || end>=n )break;
}
printf("%d\n",ans);
}
return 0;
}