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

正题

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

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

#include<cstdio>
#include<cstdlib>
#include<cstring>

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<y?x:y;}
int nowmax,nowmin;

int suan(int x,int y)
{
    nowmax=0,nowmin=2e9;
    nowmax=mmax(max[x][y],mmax(max[x+n-ci[logn]][y+n-ci[logn]],
    mmax(max[x+n-ci[logn]][y],max[x][y+n-ci[logn]])));
    nowmin=mmin(min[x][y],mmin(min[x+n-ci[logn]][y+n-ci[logn]],
    mmin(min[x+n-ci[logn]][y],min[x][y+n-ci[logn]])));
    return nowmax-nowmin;
}

int main()
{
    scanf("%d %d %d",&a,&b,&n);
    while(tot<=n)
    {
        tot*=2;
        logn++;
    }
    logn--;
    ci[0]=1;
    for(int i=1;i<=logn;i++) ci[i]=(ci[i-1]<<1);
    for(int i=1;i<=a;i++)
        for(int j=1;j<=b;j++)
        {
            scanf("%d",&max[i][j]);
            min[i][j]=max[i][j];
        }
    for(int k=1;k<=logn;k++)
        for(int i=1;i+ci[k]-1<=a;i++)
            for(int j=1;j+ci[k]-1<=b;j++)
            {
                max[i][j]=mmax(max[i][j],mmax(max[i+ci[k-1]][j+ci[k-1]],
                mmax(max[i+ci[k-1]][j],max[i][j+ci[k-1]])));
                min[i][j]=mmin(min[i][j],mmin(min[i+ci[k-1]][j+ci[k-1]],
                mmin(min[i+ci[k-1]][j],min[i][j+ci[k-1]])));
            }
    int ans=2e9;
    for(int i=1;i+n-1<=a;i++)
        for(int j=1;j+n-1<=b;j++)
            ans=mmin(ans,suan(i,j));
    printf("%d",ans);
}

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

      但是有一种更好的方法。

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

      原来你有一个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<cstdio>
#include<cstdlib>
#include<cstring>
#include<iostream>
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][list1[ed1]]) ed1--;list1[++ed1]=j;
			while(st2<=ed2 && 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]<mmin[list1[ed1]][j]) ed1--;list1[++ed1]=i;
			while(st2<=ed2 && mmax[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);
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值