題目描述:
有一個 a×b的整數組成的矩陣,現請你從中找出一個 n×n 的正方形區域,使得該區域所有數中的最大值和最小值的差最小。
輸入格式
第一行爲三個整數,分別表示 a,b,n的值;
第二行至第 a+1 行每行爲 b 個非負整數,表示矩陣中相應位置上的數。
輸出格式
輸出僅一個整數,爲 a×b 矩陣中所有“n×n 正方形區域中的最大整數和最小整數的差值”的最小值。
數據範圍
2≤a,b≤1000,
n≤a,n≤b,n≤100,
矩陣中的所有數都不超過 10^9。
輸入樣例:
5 4 2
1 2 5 6
0 17 16 0
16 17 2 1
2 10 2 1
1 2 2 2
輸出樣例:
1
分析:
本題代碼看起來複雜,寫起來卻格外的順,一遍寫完,都不用調試,提交就直接AC了,可能是腦中的模型比較清晰吧。
直接說下思路,先對矩陣逐行求區間長度是n的滑動窗口的最大最小值,分別存進最大最小值數組。然後再對求出的最值數組按列再求一次最值,二者之差就是所有n*n正方形區域內的最大整數與最小整數差了。當然,求滑動區間的最值就是使用單調隊列實現的。
如圖所示,如果要求3*3正方形中的最值,可以先將各行中長度爲3的區間的最值存在這個區間開始的格子裏,比如上圖藍色的區域,求完後,第一列的前三個格子就存了這個藍色正方形各行的最值,再豎着對第一行求下長度爲3區間的最值存到第一個單元格里,第一個單元格存儲的就是整個正方形區域內的最值了。當然我們是先把初始矩形各行長度爲n區間的最值存到一個新的數組,然後再對這個數組豎着求最值存入又一個數組來實現的,以避免值被覆蓋。實現細節見代碼:
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 1005;
int n,m,k,g[N][N],mi[N][N],ma[N][N],q[N],r[N],s[N],t[N];
void getmin(int *a,int *b,int m){//將a中區間長度是k區間最小值存入b
int hh = 0,tt = -1;
for(int i = 1;i <= m;i++){
if(hh <= tt && i - q[hh] >= k) hh++;
while(hh <= tt && a[i] <= a[q[tt]]) tt--;
q[++tt] = i;
if(i >= k) b[i - k + 1] = a[q[hh]];
}
}
void getmax(int *a,int *b,int m){//將a中區間長度是k區間最大值存入b
int hh = 0,tt = -1;
for(int i = 1;i <= m;i++){
if(hh <= tt && i - q[hh] >= k) hh++;
while(hh <= tt && a[i] >= a[q[tt]]) tt--;
q[++tt] = i;
if(i >= k) b[i - k + 1] = a[q[hh]];
}
}
int main(){
scanf("%d%d%d",&n,&m,&k);
for(int i = 1;i <= n;i++)
for(int j = 1;j <= m;j++)
scanf("%d",&g[i][j]);
for(int i = 1;i <= n;i++){//將各行的最值分別存入mi和ma數組
getmin(g[i],mi[i],m);
getmax(g[i],ma[i],m);
}
int res = 1e9;
for(int i = 1;i <= m - k + 1;i++){
for(int j = 1;j <= n;j++) r[j] = mi[j][i];//取一列的最值數組
getmin(r,s,n);//求最值數組列的最值
for(int j = 1;j <= n;j++) r[j] = ma[j][i];
getmax(r,t,n);
for(int j = 1;j <= n - k + 1;j++) res = min(res,t[j] - s[j]);//更新最值之差
}
printf("%d\n",res);
return 0;
}