RMQ-快速求区间最值
这里介绍的区间最值包括一维区间最值以及二维区间最值。刚开始写这两个板子是为了能涵盖比较全的查询功能,然而后来发现这个板子好像不是很友好的亚子,因为 R M Q RMQ RMQ这个东西在很多题中会被卡空间,所以如果不需要查询位置的话还是不要去开那个数组,建议读者根据我对 R M Q RMQ RMQ原理的阐述自己写一份模板 (记得找两个题测一下是不是正确的),这样对 R M Q RMQ RMQ思想的理解也会有很大帮助。因为还有不少知识是和这个类似的,如果你精通 R M Q RMQ RMQ的思想了的话就能举一反三了 (比如树上倍增)
一维 RMQ
R M Q ( R a n g e M i n i m u m / M a x i m u m Q u e r y RMQ(Range Minimum/Maximum Query RMQ(RangeMinimum/MaximumQuery,即区间最值查询,一维 R M Q RMQ RMQ是指这样一个问题:对于长度为 n n n的数列 A A A,回答若干次询问 R M Q ( i , j ) RMQ(i,j) RMQ(i,j),返回数列A中下标在区间 [ i , j ] [i,j] [i,j]中的最小 / / /大值。
本文介绍一种比较高效的 S T ST ST算法解决这个问题。 S T ( S p a r s e T a b l e ) ST(Sparse\ Table) ST(Sparse Table)算法可以在 O ( n l o g n ) O(nlogn) O(nlogn)时间内进行预处理,然后在 O ( 1 ) O(1) O(1)时间内回答每个查询。
-
预处理
设 A [ i ] A[i] A[i]是要求区间最值的数列, d p [ i ] [ j ] dp[i][j] dp[i][j]表示区间 [ i , i + 2 j − 1 ] [i,i+2^j-1] [i,i+2j−1]中所有数的最大值。( D P DP DP的状态)
例如:
A A A数列为: 3 2 4 5 6 8 1 2 9 7 3\ 2\ 4\ 5\ 6\ 8\ 1\ 2\ 9\ 7 3 2 4 5 6 8 1 2 9 7
d p [ 1 ] [ 2 ] dp[1][2] dp[1][2]表示区间 [ 1 , 4 ] [1,4] [1,4]最大值,即 d p [ 1 ] [ 2 ] = m a x { 3 , 2 , 4 , 5 } = 5 dp[1][2]=max\{3,2,4,5\}=5 dp[1][2]=max{3,2,4,5}=5;
并且我们可以容易的看出 d p [ i , 0 ] dp[i,0] dp[i,0]就等于 A [ i ] A[i] A[i]。( D P DP DP的初始值)
我们把 d p [ i ] [ j ] dp[i][j] dp[i][j]平均分成两段(因为 d p [ i ] [ j ] dp[i][j] dp[i][j]一定是偶数个数字),从 i i i 到 i + 2 j − 1 − 1 i + 2 ^ {j - 1} - 1 i+2j−1−1为一段, i + 2 j − 1 i + 2 ^ {j - 1} i+2j−1到 i + 2 j − 1 i + 2 ^ j - 1 i+2j−1为一段(长度都为 2 j − 1 2 ^ {j - 1} 2j−1)。于是我们得到了状态转移方程 d p [ i ] [ j ] = m a x ( d p [ i ] [ j − 1 ] , d p [ i + 2 j − 1 ] [ j − 1 ] ) dp[i][j]=max(dp[i][j-1], dp[i + 2^{j-1}][j-1]) dp[i][j]=max(dp[i][j−1],dp[i+2j−1][j−1])。
-
查询
以求区间最大值为例,假如我们需要查询的区间为 ( l , r ) (l,r) (l,r),那么我们需要找到覆盖这个闭区间(左边界取 l l l,右边界取 r r r)的最小幂(可以重复,比如查询区间 [ 1 , 5 ] [1,5] [1,5]我们可以查询 [ 1 , 4 ] [1,4] [1,4]和 [ 2 , 5 ] [2,5] [2,5]然后取最大 / / /小值)。
因为这个区间的长度为 r − l + 1 r - l + 1 r−l+1,所以我们可以取 k = log 2 ( r − l + 1 ) k=\log_2 {(r - l + 1)} k=log2(r−l+1),则有: m a x ( l , r ) = m a x { d p [ l ] [ k ] , d p [ r − 2 k + 1 ] [ k ] } max(l, r)=max\{dp[l][k], dp[ r - 2 ^ k + 1][k]\} max(l,r)=max{dp[l][k],dp[r−2k+1][k]}。
举例说明,要求区间 [ 1 , 5 ] [1,5] [1,5]的最大值, k = log 2 ( 5 − 1 + 1 ) = 2 k = \log_2(5 - 1 + 1)= 2 k=log2(5−1+1)=2,即求 m a x ( d p [ 1 ] [ 2 ] , d p [ 5 − 2 2 + 1 ] [ 2 ] ) = m a x ( d p [ 1 ] [ 2 ] , d p [ 2 ] [ 2 ] ) max(dp[1][2],dp[5 - 2 ^ 2 + 1][2])=max(dp[1][2],dp[2][2]) max(dp[1][2],dp[5−22+1][2])=max(dp[1][2],dp[2][2]);
-
模板说明
下面这个模版只一个比较全面的 R M Q RMQ RMQ板子,其包含查询最大最小值以及对应的位置,但是在实际运用中可能不需要这么多功能,而且在有些题目中这样写很有可能被卡空间,所以建议根据题目需要适当修改代码与数组空间
-
模板
#include<bits/stdc++.h>
using namespace std;
const int maxn=1e5+10;
int n,q,a[maxn],l,r;
struct st{
int dp[2][maxn][20],id[2][maxn][20];//dp[0]这一位用来弄最小值,dp[1]用来计算最大值,id用来记录对应位置
void init()
{
for(int i=1;i<=n;i++) dp[0][i][0]=dp[1][i][0]=a[i],id[0][i][0]=id[1][i][0]=i;
for(int j=1;(1<<j)<=n;j++){
for(int i=1;(i+(1<<j)-1)<=n;i++){
dp[0][i][j]=min(dp[0][i][j-1],dp[0][i+(1<<(j-1))][j-1]);
dp[1][i][j]=max(dp[1][i][j-1],dp[1][i+(1<<(j-1))][j-1]);
if(dp[0][i][j-1]<=dp[0][i+(1<<(j-1))][j-1]) id[0][i][j]=id[0][i][j-1]; //记录最值下标
else id[0][i][j]=id[0][i+(1<<(j-1))][j-1];
if(dp[1][i][j-1]>=dp[1][i+(1<<(j-1))][j-1]) id[1][i][j]=id[1][i][j-1];
else id[1][i][j]=id[1][i+(1<<(j-1))][j-1];
}
}
}
pair<int,int> query(int le,int ri) // 返回最小值与最大值,pair的first是最小值,second是最大值
{
if(le>ri) return make_pair(0,0);int k=0;
while(le+(1<<(k+1))-1<=ri) k++;
int mi=min(dp[0][le][k],dp[0][ri-(1<<k)+1][k]),ma=max(dp[1][le][k],dp[1][ri-(1<<k)+1][k]);
return make_pair(mi,ma);
}
pair<int,int> query_id(int le,int ri) //返回最小值与最大值的位置下标,前一个为最小值下标
{
if(le>ri) return make_pair(-1,-1);int k=0,mi,ma;
while(le+(1<<(k+1))-1<=ri) k++;
if(dp[0][le][k]<=dp[0][ri-(1<<k)+1][k]) mi=id[0][le][k];
else mi=id[0][ri-(1<<k)+1][k];
if(dp[1][le][k]>=dp[1][ri-(1<<k)+1][k]) ma=id[1][le][k];
else ma=id[1][ri-(1<<k)+1][k];
return make_pair(mi,ma);
}
}arr;
int main()
{
scanf("%d %d",&n,&q);
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
arr.init();
for(int i=1;i<=n;i++){
scanf("%d %d",&l,&r);
pair<int,int> ans_num=arr.query(l,r),ans_id=arr.query_id(l,r);
printf("min element in [%d,%d]:%d index:%d\n",l,r,ans_num.first,ans_id.first);
printf("max element in [%d,%d]:%d index:%d\n",l,r,ans_num.second,ans_id.second);
}
}
二维RMQ
二维 R M Q RMQ RMQ是指这样一个问题:给定二维数组 A A A,求 A A A中左上顶点为 ( a , b ) (a,b) (a,b),右下顶点为 ( c , d ) (c,d) (c,d)的子矩形中所有数的最值, S T ST ST算法可以在 O ( m n log 2 m log 2 n ) O(mn\log_2m\log_2n) O(mnlog2mlog2n)的复杂度内预处理, O ( 1 ) O(1) O(1)完成询问
-
预处理
设 A [ i ] [ j ] A[i][j] A[i][j]是要求区间最值的数组, d p [ i ] [ j ] [ k ] [ l ] dp[i][j][k][l] dp[i][j][k][l]表示左上顶点为 ( i , j ) (i,j) (i,j),右下顶点为 ( i + ( 1 < < k ) − 1 , j + ( 1 < < l ) − 1 ) (i+(1<<k)-1,j+(1<<l)-1) (i+(1<<k)−1,j+(1<<l)−1) 中所有数的最大值。( D P DP DP的状态)
例如:假设 A A A数组为:
X = ∣ 12 32 23 7 18 71 2 88 19 109 77 100 33 55 66 77 ∣ X=\left| \begin{matrix} 12 & 32 & 23 & 7\\ 18 & 71 & 2 & 88\\ 19& 109 &77 & 100\\ 33 & 55 & 66& 77\\ \end{matrix} \right| X=∣∣∣∣∣∣∣∣12181933327110955232776678810077∣∣∣∣∣∣∣∣
则 d p [ 2 ] [ 1 ] [ 1 ] [ 2 ] dp[2][1][1][2] dp[2][1][1][2]表示子矩形
Y = ∣ 18 71 2 88 19 109 77 100 ∣ Y=\left| \begin{matrix} 18 & 71 & 2 & 88\\ 19& 109 &77 & 100\\ \end{matrix} \right| Y=∣∣∣∣18197110927788100∣∣∣∣
中的所有数的大小 ( ( (左上端点 ( 2 , 1 ) (2,1) (2,1),右下端点 ( 2 + 2 1 − 1 , 1 + 2 2 − 1 ) ) (2+2^1-1,1+2^2-1)) (2+21−1,1+22−1))
类比一维 R M Q RMQ RMQ的转移方程,或许你已经知道了怎么做二维的了,也就是分成四个矩形然后合并,举个栗子:对于上面的矩阵 Y Y Y,分成下面四个矩阵合并:
Y 1 = ∣ 18 71 ∣ Y_1=\left| \begin{matrix} 18 & 71\\ \end{matrix} \right| Y1=∣∣1871∣∣
Y 2 = ∣ 12 88 ∣ Y_2=\left| \begin{matrix} 12 & 88\\ \end{matrix} \right| Y2=∣∣1288∣∣
Y 3 = ∣ 19 109 ∣ Y_3=\left| \begin{matrix} 19& 109 \\ \end{matrix} \right| Y3=∣∣19109∣∣
Y 4 = ∣ 77 100 ∣ Y_4=\left| \begin{matrix} 77 & 100\\ \end{matrix} \right| Y4=∣∣77100∣∣
于是我们得到状态转移方程:
d p [ i ] [ j ] [ k ] [ l ] = m a x { d p [ i ] [ j ] [ k − 1 ] [ l − 1 ] , d p [ i + ( 1 < < ( k − 1 ) ] [ j ] [ k − 1 ] [ l − 1 ] , d p [ i ] [ j + ( 1 < < ( l − 1 ) ) ] [ k − 1 ] [ l − 1 ] , d p [ i + ( 1 < < ( k − 1 ) ) ] [ j + ( 1 < < ( l − 1 ) ) ] [ k − 1 ] [ l − 1 ] } dp[i][j][k][l]=max\{dp[i][j][k-1][l-1],dp[i+(1<<(k-1)][j][k-1][l-1],dp[i][j+(1<<(l-1))][k-1][l-1],dp[i+(1<<(k-1))][j+(1<<(l-1))][k-1][l-1]\} dp[i][j][k][l]=max{dp[i][j][k−1][l−1],dp[i+(1<<(k−1)][j][k−1][l−1],dp[i][j+(1<<(l−1))][k−1][l−1],dp[i+(1<<(k−1))][j+(1<<(l−1))][k−1][l−1]} -
查询
类比一维的查询,对于二维的,假设待查询的子矩形左上端点为 ( a , b ) (a,b) (a,b)右下端点为 ( c , d ) (c,d) (c,d) ,那么取 k 1 = log 2 ( c − a ) k_1=\log_2(c-a) k1=log2(c−a), k 2 = log 2 ( d − b ) k_2=\log_2(d-b) k2=log2(d−b),则 a n s = m a x { d p [ a ] [ b ] [ k 1 ] [ k 2 ] , d p [ c − ( 1 < < k 1 ) + 1 ] [ b ] [ k 1 ] [ k 2 ] , d p [ a ] [ d − ( 1 < < k 2 ) + 1 ] [ k 1 ] [ k 2 ] , d p [ c − ( 1 < < k 1 ) + 1 ] [ d − ( 1 < < k 2 ) + 1 ] [ k 1 ] [ k 2 ] } ans=max\{dp[a][b][k_1][k_2],dp[c-(1<<k_1)+1][b][k_1][k_2],dp[a][d-(1<<k_2)+1][k_1][k_2],dp[c-(1<<k_1)+1][d-(1<<k_2)+1][k_1][k_2]\} ans=max{dp[a][b][k1][k2],dp[c−(1<<k1)+1][b][k1][k2],dp[a][d−(1<<k2)+1][k1][k2],dp[c−(1<<k1)+1][d−(1<<k2)+1][k1][k2]}
-
模板(POJ2019)
#include<cstdio>
#include<algorithm>
using namespace std;
const int maxn=251;
int m,n,a[maxn][maxn],dp[2][maxn][maxn][9][9];
struct st{
int dp[2][maxn][maxn][9][9];
void init()
{
for(int k=0;k<=1;k++){
for(int i=1;i<=m;i++){
for(int j=1;j<=n;j++){
dp[k][i][j][0][0]=a[i][j];
}
}
}
for(int k=0;(1<<k)<=m;k++){
for(int l=0;(1<<l)<=n;l++){
for(int i=1;i+(1<<k)-1<=m;i++){
for(int j=1;j+(1<<l)-1<=n;j++){
int x=k?k-1:0,y=l?l-1:0;
int lx=k?(1<<(k-1)):0,ly=l?(1<<(l-1)):0;
dp[0][i][j][k][l]=min(dp[0][i][j][x][y],min(dp[0][i+lx][j][x][y],min(dp[0][i][j+ly][x][y],dp[0][i+lx][j+ly][x][y])));
dp[1][i][j][k][l]=max(dp[1][i][j][x][y],max(dp[1][i+lx][j][x][y],max(dp[1][i][j+ly][x][y],dp[1][i+lx][j+ly][x][y])));
}
}
}
}
}
pair<int,int> query(int c,int d,int e,int f)
{
int k1=0,k2=0;
while(c+(1<<(k1+1))<=e) k1++;
while(d+(1<<(k2+1))<=f) k2++;
int nxtx=e-(1<<k1)+1,nxty=f-(1<<k2)+1;
int maxx=max(dp[1][c][d][k1][k2],max(dp[1][nxtx][d][k1][k2],max(dp[1][c][nxty][k1][k2],dp[1][nxtx][nxty][k1][k2])));
int minn=min(dp[0][c][d][k1][k2],min(dp[0][nxtx][d][k1][k2],min(dp[0][c][nxty][k1][k2],dp[0][nxtx][nxty][k1][k2])));
return make_pair(minn,maxx);
}
}arr;
int b,k,x,y;
int main()
{
scanf("%d %d %d",&n,&b,&k);m=n;
for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) scanf("%d",&a[i][j]);
arr.init();
for(int i=1;i<=k;i++){
scanf("%d %d",&x,&y);
pair<int,int> ans=arr.query(x,y,x+b-1,y+b-1);
printf("%d\n",ans.second-ans.first);
}
}