[HAOI2007]理想的正方形,洛谷P2216,st表或者单调队列

正题

      这题看上去很麻烦,看到取矩阵就马上想得到用st表来完成,用nm log4(nm) 就可以了,其实是很快的。

很丑的代码<半年前打的>

#include
#include
#include

int a,b,n;
int max[1010][1010],min[1010][1010];
int logn=0;
int tot=1;
int ci[31];
int mmax(int x,int y) {return x>y?x:y;}
int mmin(int x,int y) {return x

      就是先把一个个矩阵的算出来,然后在不断的读。

      但是有一种更好的方法。

      就是单调队列维护矩阵极值。

      原来你有一个n*m的矩阵,假如说你要从中取一个a*b的最大值,如果直接遍历要n*m*a*b

      现在,我们先把每一行连续a个的最大值先处理出来。

      如下图,描述样例,左边为原来的,右边的mmax[ i ][ j ]表示原数组第 i 行第 j 列 到 第 i 行第 j+n-1 列的最大值。

我们做好之后,就令ans[ i ][ j ]表示mmax数组中第 i 行第 j 列到第 i+n-1 行第 j 列的最大值。那么ans表示的就是第 i 行第 j 列到第 i+n-1 行到第 j+n-1 列的最大值。如下图。

求出来的就是这样的。

然后我们可以用一个单调队列来维护最大值,一个单调队列来维护最小值,最后用3nm的时间就可以完成操作。

#include
#include
#include
#include
using namespace std;

int n,a,b;
int s[1010][1010];
int mmax[1010][1010],mmin[1010][1010];
int totx[1010][1010],toty[1010][1010];
int list1[1010],list2[1010];
int st1=1,ed1=0,st2=1,ed2=0;

int main(){
	scanf("%d %d %d",&a,&b,&n);
	for(int i=1;i<=a;i++)
		for(int j=1;j<=b;j++)
			scanf("%d",&s[i][j]);
	for(int i=1;i<=a;i++){
		st1=1,ed1=0,st2=1,ed2=0;
		for(int j=1;j<=b;j++){
			while(st1<=ed1 && s[i][j]s[i][list2[ed2]]) ed2--;list2[++ed2]=j;
			if(j>=n){
				mmax[i][j-n+1]=s[i][list2[st2]];
				mmin[i][j-n+1]=s[i][list1[st1]];
				while(j-list1[st1]+1>=n) st1++;
				while(j-list2[st2]+1>=n) st2++;
			}
		}
	}
	for(int j=1;j<=b-n+1;j++){
		st1=1,ed1=0,st2=1,ed2=0;
		for(int i=1;i<=a;i++){
			while(st1<=ed1 && mmin[i][j]mmax[list2[ed2]][j]) ed2--;list2[++ed2]=i;
			if(i>=n){
				totx[i-n+1][j]=mmax[list2[st2]][j];
				toty[i-n+1][j]=mmin[list1[st1]][j];
				while(i-list1[st1]+1>=n) st1++;
				while(i-list2[st2]+1>=n) st2++;
			}
		}
	}
	int ans=1e9;
	for(int i=1;i<=a-n+1;i++)
		for(int j=1;j<=b-n+1;j++)
			ans=min(ans,totx[i][j]-toty[i][j]);
	printf("%d\n",ans);
}

 

你可能感兴趣的:([HAOI2007]理想的正方形,洛谷P2216,st表或者单调队列)