- 什么是RMQ、ST:RMQ(Range Minimum/Maximum Query)问题,即求区间的最值。可以写一个线段树来实现,但是每次查询的时间复杂度为O(log n),若查询次数过多则可能超时。ST算法是一种离线算法,经过O(nlogn)的预处理后,可以在O(1)的时间复杂度内进行查询,缺点是无法对数据做出修改。
-
算法实现:
初始化:用dp实现初始化。a[]为原始数据数组f,[i][j]表示从i向后的2j个数字中的最值。显然f[i][0]=a[i];
我们将f[i][j]分为两段,一段为a[i]~a[2j-1]的最值即f[i][j-1],一段为a[i+2j-1]~a[i+2j]即f[i+1<<(j-1)][j-1];这样就得到了状态转移方程f[i][j]=max/min(f[i][j-1],f[i+1<<(j-1)][j-1]);。
dp数组即f数组;
1 for(i=1;i<=n;i++){ 2 dpmax[i][0]=a[i]; 3 dpmin[i][0]=a[i];//初始化 4 }; 5 int end_j=log(n+0.0)/log(2.0);//计算j的最大值 6 int endi; 7 for(j=1;j<=end_j;j++){//注意,由于每一个dp[i,j]的求解都要用到dp[i,j-1]故j应放在外层循环 8 endi=n+1-(1<<j);//计算i的最大值 9 for(i=1;i<=endi;i++){ 10 dpmax[i][j]=max(dpmax[i][j-1],dpmax[i+(1<<(j-1))][j-1]); 11 dpmin[i][j]=min(dpmin[i][j-1],dpmin[i+(1<<(j-1))][j-1]);//dp预处理 12 } 13 }
查询:得到f数组之后,若要查询[l,r]的最值,则将区间分成两段,l~log2r与r-log2r~r,则两段的最值中较大(小)的即为答案。
1 for(i=1;i<=m;i++){ 2 scanf("%d%d",&l,&r); 3 k=log(r-l+1.0)/log(2.0);//计算分割点 4 printf("%d ",max(dpmax[l][k],dpmax[r-(1<<k)+1][k])//分两段查询 5 -min(dpmin[l][k],dpmin[r-(1<<k)+1][k])); 6 }
-
例题:POJ3264
题目大意:给出一串的数字,然后给出一个区间a b,输出从a到b的最大的数和最小的数的差。
1 #include<iostream> 2 #include<cmath> 3 #include<cstring> 4 #include<cstdio> 5 using namespace std; 6 7 int a[100000],dpmax[50000][100],dpmin[50000][100]; 8 9 int main(){ 10 std::ios::sync_with_stdio(false); 11 int i,j,m,n,k,t,l,r; 12 while(scanf("%d%d",&n,&m)!=EOF){ 13 for(i=1;i<=n;i++)scanf("%d",&a[i]); 14 for(i=1;i<=n;i++){ 15 dpmax[i][0]=a[i]; 16 dpmin[i][0]=a[i];//初始化 17 }; 18 int end_j=log(n+0.0)/log(2.0);//计算j的最大值 19 int endi; 20 for(j=1;j<=end_j;j++){//注意,由于每一个dp[i,j]的求解都要用到dp[i,j-1]故j应放在外层循环 21 endi=n+1-(1<<j);//计算i的最大值 22 for(i=1;i<=endi;i++){ 23 dpmax[i][j]=max(dpmax[i][j-1],dpmax[i+(1<<(j-1))][j-1]); 24 dpmin[i][j]=min(dpmin[i][j-1],dpmin[i+(1<<(j-1))][j-1]);//dp预处理 25 } 26 } 27 for(i=1;i<=m;i++){ 28 scanf("%d%d",&l,&r); 29 k=log(r-l+1.0)/log(2.0);//计算分割点 30 printf("%d ",max(dpmax[l][k],dpmax[r-(1<<k)+1][k])//分两段查询 31 -min(dpmin[l][k],dpmin[r-(1<<k)+1][k])); 32 } 33 return 0; 34 } 35 }