luogu P2216 [HAOI2007]理想的正方形
题面传送门
一看到最大最小值,而且是区间,立马想到两个东西:\(st\)和单调队列。这里介绍一种\(st\)表的做法。
但这可是二维,我们只学过一维的。
回想一下之前的前缀和和差分,我们是怎么一维转多维的。
维恩图!
首先是建表,肯定还是拿来拼,四个小正方形拼一个大正方形。
设\(stmax_{i,j,k}\)表示以\(i\),\(j\)为右下角端点边长为\(2^k\)的正方形内最大值为多少。
那么就可以拼了:\(stmax_{i,j,k}=max(stmax_{i,j-2^{k-1},k-1},max(stmax_{i,j,k-1},max(stmax_{i-2^{k-1},j,k-1}],stmax_{i-2^{k-1},j-2^{k-1},k-1})))\)
同理,最小值亦可以拼。\(stmin_{i,j,k}=min(stmin_{i,j-2^{k-1},k-1},min(stmin_{i,j,k-1},min(stmin_{i-2^{k-1},j,k-1}],stmin_{i-2^{k-1},j-2^{k-1},k-1})))\)
然后是查询,因为他这里查的是正方形,所以省了很多不必要的麻烦。正方形边长为\(n\)那么最后一维就可以定下来了:\(log^2(n)\)向下取整。模仿维恩图和一维\(st\),可以推出结论:
\(max(stmax_{i,j-q+2^k,k},max(stmax_{i,j,k},max(stmax_{i-q+2^k,j,k},stmax_{i-q+2^k,j-q+2^k,k}\)
同理,最小值:
\(min(stmin_{i,j-q+2^k,k},min(stmin_{i,j,k},min(stmin_{i-q+2^k,j,k},stmin_{i-q+2^k,j-q+2^k,k}\)
两个相减,第一,二维枚举,同时取最小值。
代码实现:
#include<cstdio>
using namespace std;
int n,m,q,stmin[1039][1039][10],stmax[1039][1039][10],ans=1e9,tot,pus,a[1039][1039];
inline int max(int a,int b) {
return a>b?a:b;
}
inline int min(int a,int b) {
return a<b?a:b;
}
inline void read(int &x){
char s=getchar();x=0;
while(s<'0'||s>'9') s=getchar();
while(s>='0'&&s<='9') x=(x<<3)+(x<<1)+(s^48),s=getchar();
}
int main() {
register int i,j,k;
scanf("%d%d%d",&n,&m,&q);
for(i=1; i<=n; i++) {
for(j=1; j<=m; j++) read(a[i][j]),stmax[i][j][0]=stmin[i][j][0]=a[i][j];
}
for(k=1; (1<<k)<=q; k++) {
for(i=1<<k; i<=n; i++) {
for(j=1<<k; j<=m; j++) {
stmax[i][j][k]=max(stmax[i][j-(1<<k-1)][k-1],max(stmax[i][j][k-1],max(stmax[i-(1<<k-1)][j][k-1],stmax[i-(1<<k-1)][j-(1<<k-1)][k-1])));
stmin[i][j][k]=min(stmin[i][j-(1<<k-1)][k-1],min(stmin[i][j][k-1],min(stmin[i-(1<<k-1)][j][k-1],stmin[i-(1<<k-1)][j-(1<<k-1)][k-1])));
}
}
}
k=0;
while((1<<k+1)<=q) k++;
for(i=q; i<=n; i++) {
for(j=q; j<=m; j++) {
ans=min(ans,max(stmax[i][j-q+(1<<k)][k],max(stmax[i][j][k],max(stmax[i-q+(1<<k)][j][k],stmax[i-q+(1<<k)][j-q+(1<<k)][k])))-min(stmin[i][j-q+(1<<k)][k],min(stmin[i][j][k],min(stmin[i-q+(1<<k)][j][k],stmin[i-q+(1<<k)][j-q+(1<<k)][k]))));
}
}
printf("%d",ans);
return 0;
}