【洛谷P2216】[HAOI2007]理想的正方形

理想的正方形

  【题目描述】

  一个a*b的矩阵,从中取一个n*n的子矩阵,使所选矩阵中的最大数与最小数的差最小。

  思路:

  二维的滑动窗口

  对于每行:用一个单调队列维护,算出每个长度为n的区间的最大值和最小值,分别存在两个数组fmin和fmax中,fmax[i][j]表示第i行区间[j,j+n-1]的最大值。

  对于每列:用一个单调队列维护,算出fmax和fmin数组中纵列每个长度为n的区间的最大值和最小值,分别存在两个数组ffmin和ffmax中,

ffmax[i][j]表示以(i,j)为左上端点的大小为n*n的矩阵中的最大值。

  扫一遍ffmax[1~a-n+1][1~b-n+1]和ffmin[1~a-n+1][1~b-n+1]的差,得出ans。

 

  单调队列原理:

  以维护最大值为例:

  对于每个新加入区间的值,显而易见的是:对于向右移动的“窗口”,即当前长度为n的区间中,若存在data[q[i]]比data[q[j]]大且q[i]>q[j](q[i]表示队列中的第i个元素的编号,data[i]表示编号为i的元素的值),则可以保证q[j]在以后的区间取最大值时是不会产生影响的,我们便可以将q[j]删除,从而得到更加优秀的时间复杂度,所以,当一个新的值入队时,便可以将在其前面入队且值比它小的元素删除。 我们便可以发现可以用一个单调队列维护。 当然,对于不在当前区间内的“老”元素,要把它从队列中删除。

 

贴C++代码:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int a,b,n;
int g[1001][1001],fmin[1001][1001],fmax[1001][1001],ffmin[1001][1001],ffmax[1001][1001],queue[1001],head,tail,i,j,ans=0x7fffffff;
int main()
{
    scanf("%d%d%d",&a,&b,&n);
    for(i=1;i<=a;i++)
     for(j=1;j<=b;j++)
      scanf("%d",&g[i][j]);
    for(i=1;i<=a;i++)    //枚举每行  单调递减求区间最大值 
    {
        head=1;tail=0;
        memset(queue,0,sizeof(queue));
        for(j=1;j<n;j++)
        {
            while(tail>0&&g[i][queue[tail]]<g[i][j]) tail--;
            queue[++tail]=j;
        }
        for(j=n;j<=b;j++)
        {
            while(tail>=head&&g[i][queue[tail]]<g[i][j]) tail--;
            queue[++tail]=j;
            if(queue[head]<j-n+1) head++;
            fmax[i][j-n+1]=g[i][queue[head]];
        }
    }
    for(i=1;i<=a;i++)    //枚举每行  单调递增求区间最小值 
    {
        head=1;tail=0;
        memset(queue,0,sizeof(queue));
        for(j=1;j<n;j++)
        {
            while(tail>0&&g[i][queue[tail]]>g[i][j]) tail--;
            queue[++tail]=j;
        }
        for(j=n;j<=b;j++)
        {
            while(tail>=head&&g[i][queue[tail]]>g[i][j]) tail--;
            queue[++tail]=j;
            if(queue[head]<j-n+1) head++; 
            fmin[i][j-n+1]=g[i][queue[head]];
        }
    }
    for(i=1;i<=b-n+1;i++)    //枚举每列  单调递减求区间最大值
    {
        head=1;tail=0;
        memset(queue,0,sizeof(queue));
        for(j=1;j<n;j++)
        {
            while(tail>0&&fmax[queue[tail]][i]<fmax[j][i]) tail--;
            queue[++tail]=j;
        }
        for(j=n;j<=a;j++)
        {
            while(tail>=head&&fmax[queue[tail]][i]<fmax[j][i]) tail--;
            queue[++tail]=j;
            if(queue[head]<j-n+1) head++;
            ffmax[j-n+1][i]=fmax[queue[head]][i];
        }
    }
    for(i=1;i<=b-n+1;i++)    //枚举每列  单调递增求区间最小值 
    {
        head=1;tail=0;
        memset(queue,0,sizeof(queue));
        for(j=1;j<n;j++)
        {
            while(tail>0&&fmin[queue[tail]][i]>fmin[j][i]) tail--;
            queue[++tail]=j;
        }
        for(j=n;j<=a;j++)
        {
            while(tail>=head&&fmin[queue[tail]][i]>fmin[j][i]) tail--;
            queue[++tail]=j;
            if(queue[head]<j-n+1) head++;
            ffmin[j-n+1][i]=fmin[queue[head]][i];
        }
    }
    for(i=1;i<=a-n+1;i++)
     for(j=1;j<=b-n+1;j++)
      ans=min(ans,ffmax[i][j]-ffmin[i][j]);
    printf("%d\n",ans);
    return 0;
}

 

posted @ 2018-03-01 21:11  yjk  阅读(117)  评论(0编辑  收藏  举报