二维RMQ问题
前置知识
问题引入
- 题目地址IN
对于一个的矩阵,每个格子有一个值,有个询问,每次询问你一个子矩阵中的最大值。
- 暴力
每次花子矩阵大小的复杂度去查询。
复杂度最坏
- 改进
我们用棵线段树或者树状数组来维护每行区间最大值,复杂度最坏,还是没法过,有没有更快的方法呢?
- 分析
既然是静态的询问,没有修改操作,那么很容易联想到中的表。
那么暴力的表就是和数据结构的做法类似,预处理个表,每次在多个表中查询最大值,复杂度最坏为,虽然能过更多的数据,但是还是不够。
- 二维表
既然查询对象是个二维矩阵,那么我们能不能维护一个二维的表呢?答案显然是肯定的。
预处理:
所以我们令,为新的表,表示以为左上角,右下角为的矩阵中的最大值,那么我们可以看出预处理的复杂度会是,所以对于询问数交少的还是用数据结构预处理查询比较好。
然后我们来看,对于每个,可以由哪些状态更新。
我们来看这个状态表示的矩阵,如下图:
假设这里左上角的点为,右下角点为,那么我们可以把它分成两部分,如下图:
那么我们将点看作,其实原来的大矩阵就可以由分成的这两个小矩阵更新得到,转移如下:
其中,里面第一个为上半部分矩阵,后面一个为下半部分矩阵。
如果的话,就将其竖起剖成两部分即可,是同理的。转移如下:
所以最后按照从小到大更新即可。
查询
对于一个子矩阵,我们假设它的左上角坐标为,右下角为,那么可以通过预处理的二维表,将其分成四部分查询,如下图:
其中四个部分为图中,查询区间是可以重合的。
其实就对应了如下四个预处理的状态,我们令:
答案就为上面四个矩阵的,所以预处理对数,每次回答即可。
那么总的复杂度为,是可以过的了。
代码:
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int Log=12;
const int N=610;
const int inf=1e9;
int n,m,Q;
int maxv[Log][Log][N][N];
int pre[N],val[N][N];
void init(){
for(int i=1;i<=n;i++)for(int j=1;j<=m;j++)maxv[0][0][i][j]=val[i][j];
pre[2]=pre[3]=1;
for(int i=4,up=max(n,m);i<=up;i++)pre[i]=pre[i>>1]+1;
int up1=pre[n]+1,up2=pre[m]+1;
for(int l1=0;l1<=up1;l1++){
for(int l2=0;l2<=up2;l2++){
if(!l1&&!l2) continue;
for(int i=1;(i+(1<<l1)-1)<=n;i++){
for(int j=1;(j+(1<<l2)-1)<=m;j++){
if(l2)maxv[l1][l2][i][j]=max(maxv[l1][l2-1][i][j],maxv[l1][l2-1][i][j+(1<<(l2-1))]);
else maxv[l1][l2][i][j]=max(maxv[l1-1][l2][i][j],maxv[l1-1][l2][i+(1<<(l1-1))][j]);
}
}
}
}
}
int query(int x1,int y1,int x2,int y2){
int p=pre[x2-x1+1],q=pre[y2-y1+1];
int ans=-inf;
ans=max(maxv[p][q][x1][y1],maxv[p][q][x1][y2-(1<<q)+1]);
ans=max(ans,max(maxv[p][q][x2-(1<<p)+1][y1],maxv[p][q][x2-(1<<p)+1][y2-(1<<q)+1]));
return ans;
}
int x1,x2,y1,y2;
int main(){
scanf("%d%d%d",&n,&m,&Q);
for(int i=1;i<=n;i++)for(int j=1;j<=m;j++)scanf("%d",&val[i][j]);
init();
for(int i=1;i<=Q;i++){
scanf("%d%d%d%d",&x1,&y1,&x2,&y2);
printf("%d\n",query(x1,y1,x2,y2));
}
return 0;
}