[HAOI2007]理想的正方形

题目

Description

有一个a*b的整数组成的矩阵,现请你从中找出一个n*n的正方形区域,使得该区域所有数中的最大值和最小值的差最小。

Input

第一行为3个整数,分别表示a,b,n的值第二行至第a+1行每行为b个非负整数,表示矩阵中相应位置上的数。每行相邻两数之间用一空格分隔。
100%的数据2<=a,b<=1000,n<=a,n<=b,n<=1000

Output

仅一个整数,为a*b矩阵中所有“n*n正方形区域中的最大整数和最小整数的差值”的最小值。

Sample Input

5 4 2
1  2  5  6
0  17 16 0
16 17 2  1
2  10 2  1
1  2  2  2

Sample Output

1

思路

根据题目样例的意思;

1     2    5    6

0    17  16   0

16  17   2    1

2    10   2    1

1     2    2    2

我们可以求出每横排 n 个宽度的最大值;

也就是这样

 2      5     6

17    17   16

17    17    2

10    10    2

 2      2     2

然后再求竖排 n 个宽度的最大值;

也就是

17    17    16

17    17    16

17    17     2

10    10     2

这样每个点就代表一个n*n 的正方形;

然后用同样的方法求一波每个正方形代表的最小值;

最后,根据题意求出

n*n正方形区域中的最大整数和最小整数的差值 的最小值

也就是把上面每个点的值相减,然后求一波 min 就ok了;

代码

#include<bits/stdc++.h>
#define re register
typedef long long ll;
using namespace std;
inline ll read()
{
    ll a=0,f=1; char c=getchar();
    while (c<'0'||c>'9') {if (c=='-') f=-1; c=getchar();}
    while (c>='0'&&c<='9') {a=a*10+c-'0'; c=getchar();}
    return a*f; 
}
ll a,b,n;
ll map1[1010][1010];
ll val[1010];
ll mx[1010][1010],mn[1010][1010];
ll ans[1010][1010],sum[1010][1010];
ll q[1010];
int main()
{
    a=read(); b=read(); n=read();
    for(re ll i=1;i<=a;i++)
    for(re ll j=1;j<=b;j++)
        map1[i][j]=read();//读入 
    ll tot=0;
    for(re ll k=1;k<=a;k++)//k 代表每一行 
    {
        ll head=1,tail=0;
        tot=0;
        for(re ll i=1;i<=b;i++)// i 是每一列 
        {
            while(head<=tail&&i-q[head]+1>n)//如果队列长度大于n 那么踢队头 
                head++;
            while(head<=tail&&map1[k][i]>map1[k][q[tail]])//严格单调下降 
                tail--;
            q[++tail]=i;
            if(i>=n)
                mx[k][++tot]=map1[k][q[head]];//单调队列板子,横排求一波最大值; 
            //k 代表第几横排 ,tot 是第几个 n 宽度的max 
        }
    }
    //同上,横排求一波最小值; 
    ll cnt=0;
    for(re ll k=1;k<=a;k++)
    {
        ll head=1,tail=0;
        cnt=0;
        for(re ll i=1;i<=b;i++)
        {
            while(head<=tail&&i-q[head]+1>n)//如果队列长度大于n 那么踢队头 
                head++;
            while(head<=tail&&map1[k][i]<map1[k][q[tail]])
                tail--;
            q[++tail]=i;
            if(i>=n)
                mn[k][++cnt]=map1[k][q[head]];
            //k 代表第几横排 ,tot 是第几个 n 宽度的min 
        }
    }
    ll s=0;
    for(re ll t=1;t<=tot;t++)//横排的max 个数 
    {
        s=0;
        for(re ll k=1;k<=a;k++)
            val[k]=mx[k][t];//将每一竖排的之前求出的max 值记下; 
        ll head=1,tail=0;
        for(re ll i=1;i<=a;i++)
        {
            while(head<=tail&&i-q[head]+1>n)//如果队列长度大于n 那么踢队头 
                head++;
            while(head<=tail&&val[i]>val[q[tail]])//单调队列板子,竖排求一波最大值; 
                tail--;
            q[++tail]=i;
            if(i>=n)
                ans[t][++s]=val[q[head]];
            //t 代表第几横排 ,s 是竖排第几个 n 宽度的max
        }
    }
    memset(sum,127/3,sizeof(sum));
    ll ss=0;
    //同上 ,求一波最小值; 
    for(re ll t=1;t<=cnt;t++)
    {
        ss=0;
        for(re ll k=1;k<=a;k++)
            val[k]=mn[k][t];
        ll head=1,tail=0;
        for(re ll i=1;i<=a;i++)
        {
            while(head<=tail&&i-q[head]+1>n)//如果队列长度大于n 那么踢队头 
                head++;
            while(head<=tail&&val[i]<val[q[tail]])
                tail--;
            q[++tail]=i;
            if(i>=n)
                sum[t][++ss]=val[q[head]];
            //t 代表第几横排 ,s 是竖排第几个 n 宽度的min 
        }
    }
    ll answer=1<<30;
    for(re ll i=1;i<=tot;i++)
    for(re ll j=1;j<=s;j++)//枚举每个横排长度为 n 的 个数 和竖排的长度为 n 的个数  
        answer=min(answer,ans[i][j]-sum[i][j]);//每个n*n区域的max与min 之差 求最小值 
    printf("%lld
",answer);
}
原文地址:https://www.cnblogs.com/wzx-RS-STHN/p/13419103.html