RMQ (Range Minimum/Maximum Query)问题
含义:对于长度为n的数列A,回答若干询问RMQ(A,i,j)(i,j<=n),返回数列A中下标在i,j里的最小(大)值,也就是说,RMQ问题是指求区间最值的问题。
时间复杂度:预处理时间复杂度O(n*log(n)),查询O(1)
基础:dp
F[i][j]表示从第i位起,2^j个数中的最值。
那么dp的初值是什么?
显然f[i][0]的值为a[i]。
Dp的方程呢?
对于i~i+2^j-1这段区间,我们将它分为长度相同的两部分:
显然2^j=2^(j-1)+2^(j-1),所以分为i~i+2^(j-1)-1, i+2^(j-1)~i+2^j-1;
那么f[i][j]=max/min(f[i][j-1],f[i+2^(j-1)][j-1])。
所以当查询一段区间[l,r]时:
Int k=trunc(log2(r-l+1));
Int ans=max/min(f[l][k],f[r-2^k+1][k]);
有三道例题热身:
#1巴蜀oj1939
Description
Input
Output
Sample Input
3 2 4 5 6 8 1 2 9 7
1 8
2 9
Sample Output
9
#include<iostream>
#include<cstdio>
#include<cmath>
using namespace std;
int n,k,a[1000005];
int f[1000005][25];
void rmq_prepare()
{
for(int i=1;i<=n;i++)
f[i][0]=a[i];
for(int j=1;(1<<j)<=n;j++)
for(int i=1;i+(1<<j)-1<=n;i++)
f[i][j]=max(f[i][j-1],f[i+(1<<(j-1))][j-1]);
}
int query(int l,int r)
{
int k=trunc(log2(r-l+1));
return max(f[l][k],f[r-(1<<k)+1][k]);
}
int main()
{
scanf("%d%d",&n,&k);
for(int i=1;i<=n;i++)
scanf("%d",&a[i]);
rmq_prepare();
int l,r;
for(int i=1;i<=k;i++)
{
scanf("%d%d",&l,&r);
printf("%d\n",query(l,r));
}
return 0;
}
#2 poj3264
Description
Input
Output
Sample Input
1
7
3
4
2
5
1 5
4 6
2 2
Sample Output
3
0
#include<iostream>
#include<cstdio>
#include<cmath>
using namespace std;
int n,m,a[50005];
int fmi[50005][20];
int f[50005][20];
void st_prepare()
{
for(int i=1;i<=n;i++)fmi[i][0]=a[i],f[i][0]=a[i];
for(int j=1;(1<<j)<=n;j++)
for(int i=1;i+(1<<j)-1<=n;i++)
fmi[i][j]=min(fmi[i][j-1],fmi[i+(1<<(j-1))][j-1]),
f[i][j]=max(f[i][j-1],f[i+(1<<(j-1))][j-1]);
}
int query(int l,int r)
{
int k=trunc(log2(r-l+1));
return (max(f[l][k],f[r-(1<<k)+1][k])-min(fmi[l][k],fmi[r-(1<<k)+1][k]));
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
st_prepare();
int l,r;
for(int i=1;i<=m;i++)
{
scanf("%d%d",&l,&r);
printf("%d\n",query(l,r));
}
return 0;
}
#3 poj3368
Description
Input
Output
Sample Input
4
3
Hint
对于30的数据,1≤n,q≤1000;
对于100的数据,1≤n,q≤1000000;
这道终于不太裸了。。。
f数组表示是连续的相同数块中的第几位。
例如:题目中的数据
a:-1 -1 1 1 1 1 3 10 10 10
f:1 2 1 2 3 4 1 1 2 3
对于每个询问[l,r]
首先找到在序列中和a[l]值相同且连续的最大位置,即下面代码中的t。
那么对于[t,r]这段区间中,f[i]的最大值就是频率最大的数的频率。
用rmq处理最大值。
再用它与(t-l)比较,更新答案。
下面贴出代码:
#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
using namespace std;
int n,q;
int m[100005][20];
int f[100005];
int a[100005];
void st_prepare()
{
for(int i=1;i<=n;i++)m[i][0]=f[i];
for(int j=1;(1<<j)<=n;j++)
for(int i=1;i-1+(1<<j)<=n;i++)
m[i][j]=max(m[i][j-1],m[i+(1<<(j-1))][j-1]);
}
int rmq(int l,int r)
{
if(l>r)return 0;
int k=trunc(log2(r-l+1));
return max(m[l][k],m[r-(1<<k)+1][k]);
}
int main()
{
while(scanf("%d",&n)&&n)
{
scanf("%d",&q);
memset(a,0,sizeof(a));
a[0]=1005;
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
if(a[i]==a[i-1])
f[i]=f[i-1]+1;
else f[i]=1;
}
st_prepare();
for(int i=1;i<=n;i++)cout<<f[i]<<endl;
int l,r;
for(int i=1;i<=q;i++)
{
scanf("%d%d",&l,&r);
int t=l;
while(t<=r&&a[t]==a[t-1])
t++;
printf("%d\n",max(rmq(t,r),t-l));
}
}
return 0;
}