1047: [HAOI2007]理想的正方形

Time Limit: 10 Sec  Memory Limit: 162 MB
Submit: 3311  Solved: 1819
[Submit][Status][Discuss]

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
分析:数据这么大,直接搜肯定是不行的.可以发现这道题就是滑动的窗口的二维版本,只不过要同时求出最大值和最小值罢了,那么就可以用单调队列来求.
那么二维的单调队列该怎么写呢?其实对于一维转二维的问题,我们只需要先单独处理行或列,然后将处理好的看作是一个元素,然后转化为一维进行处理.
在这里,我们先求出每一列的单调队列,用数组记录下以(i,j)为最下面的一个元素的长度为n的当前列的元素的最大值或最小值(有点绕),然后利用求出来的数组在每一行上求单调队列,最后统计出答案.
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>

const int inf = 1000000000;
using namespace std;

int a, b, n, map[1010][1010], q1[1010], q2[1010], num1[1010], num2[1010], maxx[1010][1010], minx[1010][1010], ans = inf;

void solve1()
{
    int l1, r1, l2, r2;
    for (int j = 1; j <= b; j++)
    {
        l1 = l2 = 1;
        r1 = r2 = 0;
        for (int i = 1; i <= a; i++)
        {
            //队列1
            while (l1 <= r1 && i - num1[l1] >= n)
                l1++;
            while (l1 <= r1 && q1[r1] < map[i][j])
                r1--;
            q1[++r1] = map[i][j];
            num1[r1] = i;
            //队列2
            while (l2 <= r2 && i - num2[l2] >= n)
                l2++;
            while (l2 <= r2 && q2[r2] > map[i][j])
                r2--;
            q2[++r2] = map[i][j];
            num2[r2] = i;
            if (i >= n)
            {
                maxx[i][j] = q1[l1];
                minx[i][j] = q2[l2];
            }
    
        }
    }
}

void solve2()
{
    int l1, r1, l2, r2;
    for (int i = 1; i <= a; i++)
    {
        l1 = l2 = 1;
        r1 = r2 = 0;
        for (int j = 1; j <= b; j++)
        {
            while (l1 <= r1 && j - num1[l1] >= n)
                l1++;
            while (l1 <= r1 && q1[r1] < maxx[i][j])
                r1--;
            q1[++r1] = maxx[i][j];
            num1[r1] = j;

            while (l2 <= r2 && j - num2[l2] >= n)
                l2++;
            while (l2 <= r2 && q2[r2] > minx[i][j])
                r2--;
            q2[++r2] = minx[i][j];
            num2[r2] = j;
            if (i >= n && j >= n)
                ans = min(ans, q1[l1] - q2[l2]);
        }
    }
}

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", &map[i][j]);
    solve1();
    solve2();
    printf("%d", ans);

    return 0;
}