「RMQ」快速求区间最值

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 RMQRangeMinimum/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) STSparse 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+2j1]中所有数的最大值。( 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+2j11为一段, i + 2 j − 1 i + 2 ^ {j - 1} i+2j1 i + 2 j − 1 i + 2 ^ j - 1 i+2j1为一段(长度都为 2 j − 1 2 ^ {j - 1} 2j1)。于是我们得到了状态转移方程 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][j1],dp[i+2j1][j1])

  • 查询

    以求区间最大值为例,假如我们需要查询的区间为 ( 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 rl+1,所以我们可以取 k = log ⁡ 2 ( r − l + 1 ) k=\log_2 {(r - l + 1)} k=log2(rl+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[r2k+1][k]}

    举例说明,要求区间 [ 1 , 5 ] [1,5] [1,5]的最大值, k = log ⁡ 2 ( 5 − 1 + 1 ) = 2 k = \log_2(5 - 1 + 1)= 2 k=log2(51+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[522+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+211,1+221))
    类比一维 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][k1][l1],dp[i+(1<<(k1)][j][k1][l1],dp[i][j+(1<<(l1))][k1][l1],dp[i+(1<<(k1))][j+(1<<(l1))][k1][l1]}

  • 查询

    类比一维的查询,对于二维的,假设待查询的子矩形左上端点为 ( 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(ca) k 2 = log ⁡ 2 ( d − b ) k_2=\log_2(d-b) k2=log2(db),则 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);
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值