luogu2216 [HAOI2007] 理想的正方形
题目大意
有一个a*b的整数组成的矩阵,现请你从中找出一个n*n的正方形区域,使得该区域所有数中的最大值和最小值的差最小。
思路
我们从暴力入手,每次枚举每一个正方形,然后在这个正方形里枚举每一个点,得到最大最小值。时间复杂度在枚举正方形内的点上增加了不少,如何优化?我们很容易想到单调队列。问题就在于怎么用。
我自己有两个思路:一是当正方形在从左向右滑动时,对每一行建立单调队列跟随正方形一起滑动,要取整个正方形内的最值,在每一个单调队列的最值里取最值。但是枚举每一个单调队列的时间复杂度又上来了。怎么办?我的另外一个思路是整一个单调队列套单调队列。但这是自己发明的算法,不可尝试。
我们看到我自己的思路都是想一边滑动正方形一边得结果。我们可以考虑把整个矩阵的结果统一处理出来最后扫描一遍整个矩阵得到结果。因此,我们可以先对每一排用单调队列处理出每个点向左长度为n的区域内的最值(队列中下标对应的值b是原矩阵中的值a),随后对每一列用单调队列处理出左上方n*n面积区域内的最值(队列中下标对应的值c是其对应的排上的最值b)。这样即可求解。
#include <cstdio> #include <cstring> #include <algorithm> using namespace std; const int MAX_ROW = 1010, INF = 0x3f3f3f3f; int TotRow, TotCol, EdgeLen; int A[MAX_ROW][MAX_ROW]; int RowMax[MAX_ROW][MAX_ROW], RecMax[MAX_ROW][MAX_ROW]; int RowMin[MAX_ROW][MAX_ROW], RecMin[MAX_ROW][MAX_ROW]; struct Window { private: struct Queue { private: int A[MAX_ROW]; int head, tail; public: void init() { head = tail = 0; } int back() { return A[tail - 1]; } int front() { return A[head]; } void pop_back() { tail--; } void push_back(int x) { A[tail++] = x; } void pop_front() { head++; } bool empty() { return head == tail; } }IdQ; int Tail, Len; int (*Id_Val) (int);//GetValById bool (*CmpVal) (int, int);//> or < public: void Init(int len, int (*id_val) (int), bool (*cmpVal) (int, int)) { IdQ.init(); Len = len; Tail = 0; Id_Val = id_val; CmpVal = cmpVal; } void Move() { Tail++; int head = Tail - Len + 1; while (head > 0 && !IdQ.empty() && IdQ.front() < head) IdQ.pop_front(); while (!IdQ.empty() && !CmpVal(Id_Val(IdQ.back()), Id_Val(Tail))) IdQ.pop_back(); IdQ.push_back(Tail); } int GetM() { return Id_Val(IdQ.front()); } }w1, w2; bool Gt(int a, int b) { return a >= b; } bool Lt(int a, int b) { return a <= b; } int CurRow; int GetColValInRow(int col) { return A[CurRow][col]; } void SlideRow(int row) { CurRow = row; w1.Init(EdgeLen, GetColValInRow, Gt); w2.Init(EdgeLen, GetColValInRow, Lt); for (int col = 1; col <= TotCol; col++) { w1.Move(); RowMax[row][col] = w1.GetM(); w2.Move(); RowMin[row][col] = w2.GetM(); } } int CurCol; int GetRowMaxInCol(int row) { return RowMax[row][CurCol]; } int GetRowMinInCol(int row) { return RowMin[row][CurCol]; } void SlideCol(int col) { CurCol = col; w1.Init(EdgeLen, GetRowMaxInCol, Gt); w2.Init(EdgeLen, GetRowMinInCol, Lt); for (int row = 1; row <= TotRow; row++) { w1.Move(); RecMax[row][col] = w1.GetM(); w2.Move(); RecMin[row][col] = w2.GetM(); } } int GetAns() { int ans = INF; for (int row = EdgeLen; row <= TotRow; row++) for (int col = EdgeLen; col <= TotCol; col++) ans = min(ans, RecMax[row][col] - RecMin[row][col]); return ans; } int main() { scanf("%d%d%d", &TotRow, &TotCol, &EdgeLen); for (int row = 1; row <= TotRow; row++) for (int col = 1; col <= TotCol; col++) scanf("%d", &A[row][col]); for (int row = 1; row <= TotRow; row++) SlideRow(row); for (int col = 1; col <= TotCol; col++) SlideCol(col); printf("%d\n", GetAns()); return 0; }