bzoj1047 [HAOI2007]理想的正方形
1047: [HAOI2007]理想的正方形
Time Limit: 10 Sec Memory Limit: 162 MBSubmit: 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
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; }