先言
若不是看了题解,我倒还真不认为是单调队列优化 (DP) , 关于正解的话,不清楚,但是这道题 (RMQ) , 线段树 + (O_2) 或者 单调队列优化都可以过,(笔者没有挨个实现)
description
在一个 (n imes m) 的矩阵中,找出一个 (k imes k) 的一个正方形,并使取得的正方形的极值差最小 。 (n , m leq 10^3 , k leq 100)
solution
更优质解答 : link
首先这道题是有一个很朴素的写法 :
以最大值为例 :
- (Max_{i,j,k}) 表示以 (i,j) 节点为左上角,边长为 (k) 的最大值
(Max_{i,,j,k} = max(Max_{i,j,k - 1} , Max_{i + 1 , j + 1 , k - 1} , Max_{i + 1 , j , k - 1} , Max_{i , j + 1 , k - 1}))
分别对应着:
- 我们可以继续扩展当前的这个矩形 (Max_{i,j,k - 1})
- 我们可以继续将左端点向右下平移,给当前矩形增加一行和一列的扩展空间 : (Max_{i + 1 , j + 1 , k - 1})
- 向下,向右 , 分别对应着 (Max_{i + 1 , j , k - 1} , Max_{i , j + 1 , k - 1})
然后我们发现这个是一个 (O(n imes m imes k)) , (k) 直接枚举去掉即可。 不需要 (k) 那一维了。
(Code)
for (int k = 2; k <= n; k++)
for (int i = 1; i < a; i++)
for (int j = 1; j < b; j++)
{
Max[i][j] = max(a[i][j], max(Max[i+1][j+1], max(Max[i+1][j], Max[i][j+1])));
Min[i][j] = min(a[i][j], Min(minv[i+1][j+1], min(minv[i+1][j], Min[i][j+1])));
}
最后取答案的时候,注意节点是从 (0 o n - k + 1) 的,毕竟是左节点,毕竟在这里看了 (20min) 的错误,显然是有发言权的。
枚举整个矩形这是必不可少的,也就是 (O(n imes m)) 的复杂度是必然少不了的。我们需要简化掉取最小值和最大值,这里用的是单调对列的优化, 我们用对行和列分别建立单调队列,从而在枚举整个矩形的时候直接给整出来就 (OK) 了,这个特别像滑动窗口 。
说明一下代码内的意思 :
带 (1) 的表示最大值 ,带 (2) 表示最小值
- (f1_{i,j}) 表示 第 (i) 行,第 $j o j + n -1 $ 的最大值,同理 (f2) 为最小值
- (fm1_{i,j}) 表示以 (i,j) 为开始的左节点的最大值 ,同理 (fm2_{i,j}) 为最小值
所以我们在实现的时候通过两个循环,分别搞出 (f1,f2) 和 (fm1,fm2) 来, 最后统计答案,注意节点。
关于这道题的模拟过程,见 : 题解 P2216 【[HAOI2007]理想的正方形】
Code
Force
//这个能拿 70 分,真的是纯暴力, 来口 (O_2) 就 (A) 了。
/*
by : Zmonarch
知识点:
*/
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
#include <queue>
#include <stack>
#include <set>
#include <map>
#include <vector>
#define int long long
#define qwq register
#define inf 2147483647
const int kmaxn = 1e3 + 10 ;
const int kmod = 998244353 ;
namespace Base
{
inline int Min(int a , int b) {return a < b ? a : b ;}
inline int Max(int a , int b) {return a < b ? b : a ;}
inline int Abs(int a) {return a < 0 ? - a : a ;}
inline int Gcd(int a , int b) {return !b ? a : Gcd(b , a % b);}
}
inline int read()
{
int x = 0 , f = 1 ; char ch = getchar() ;
while(!isdigit(ch)) {if(ch == '-') f = - 1 ; ch = getchar() ;}
while( isdigit(ch)) {x = x * 10 + ch - '0' ; ch = getchar() ;}
return x * f ;
}
int n , m , k ;
int a[kmaxn][kmaxn] , f1[kmaxn][kmaxn] , f2[kmaxn][kmaxn] ;
signed main()
{
n = read() , m = read() , k = read() ;
for(qwq int i = 1 ; i <= n ; i++)
for(qwq int j = 1 ; j <= m ; j++)
f1[i][j] = f2[i][j] = a[i][j] = read() ;
for(qwq int l = 2 ; l <= k ; l++)
for(qwq int i = 1 ; i < n ; i++)
for(qwq int j = 1 ; j < m ; j++)
{
f1[i][j] = Base::Max(a[i][j] , Base::Max(f1[i + 1][j + 1] , Base::Max(f1[i + 1][j] , f1[i][j + 1]))) ;
f2[i][j] = Base::Min(a[i][j] , Base::Min(f2[i + 1][j + 1] , Base::Min(f2[i + 1][j] , f2[i][j + 1]))) ;
}
int ans = inf ;
for(qwq int i = 1 ; i <= n - k + 1 ; i++)
for(qwq int j = 1 ; j <= m - k + 1 ; j++)
ans = Base::Min(ans , f1[i][j] - f2[i][j]) ;
printf("%lld
" , ans) ;
return 0 ;
}
单调队列优化 Code
/*
by : Zmonarch
知识点:
*/
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
#include <queue>
#include <stack>
#include <set>
#include <map>
#include <vector>
#define int long long
#define inf 2147483647
#define qwq register
const int kmaxn = 1e3 + 10 ;
const int kmod = 998244353 ;
namespace Base
{
inline int Min(int a , int b) {return a < b ? a : b ;}
inline int Max(int a , int b) {return a < b ? b : a ;}
inline int Abs(int a) {return a < 0 ? - a : a ;}
inline int Gcd(int a , int b) {return !b ? a : Gcd(b , a % b);}
}
inline int read()
{
int x = 0 , f = 1 ; char ch = getchar() ;
while(!isdigit(ch)) {if(ch == '-') f = - 1 ; ch = getchar() ;}
while( isdigit(ch)) {x = x * 10 + ch - '0' ; ch = getchar() ;}
return x * f ;
}
int n , m , k , l1 , r1 , l2 , r2;
int a[kmaxn][kmaxn] , f1[kmaxn][kmaxn] , f2[kmaxn][kmaxn] , q1[kmaxn] , q2[kmaxn] , fm1[kmaxn][kmaxn] , fm2[kmaxn][kmaxn];
signed main()
{
n = read() , m = read() , k = read() ;
for(qwq int i = 1 ; i <= n ; i++)
for(qwq int j = 1 ; j <= m ; j++)
a[i][j] = read() ;
for(qwq int i = 1 ; i <= n ; i++)
{
l1 = l2 = 1 ; r1 = r2 = 0 ;
for(qwq int j = 1 ; j <= m ; j++)
{
while(j - q1[l1] >= k) l1++ ;
while(j - q2[l2] >= k) l2++ ;
while(l1 <= r1 && a[i][j] >= a[i][q1[r1]]) r1-- ;
while(l2 <= r2 && a[i][j] <= a[i][q2[r2]]) r2-- ;
q1[++r1] = q2[++r2] = j ;
if(j >= k)
f1[i][j - k + 1] = a[i][q1[l1]] , f2[i][j - k + 1] = a[i][q2[l2]] ;
}
}
for(qwq int i = 1 ; i <= m - k + 1 ; i++)
{
l1 = l2 = 1 ; r1 = r2 = 0 ;
for(qwq int j = 1 ; j <= n ; j++)
{
while(j - q1[l1] >= k) l1++ ;
while(j - q2[l2] >= k) l2++ ;
while(l1 <= r1 && f1[j][i] >= f1[q1[r1]][i]) r1-- ;
while(l2 <= r2 && f2[j][i] <= f2[q2[r2]][i]) r2-- ;
q1[++r1] = q2[++r2] = j;
if(j >= k)
fm1[j - k + 1][i] = f1[q1[l1]][i] , fm2[j - k + 1][i] = f2[q2[l2]][i] ;
}
}
int ans = inf ;
for(qwq int i = 1 ; i <= n - k + 1 ; i++)
for(qwq int j = 1 ; j <= m - k + 1 ; j++)
ans = Base::Min(fm1[i][j] - fm2[i][j] , ans) ;
printf("%lld
" , ans) ;
return 0 ;
}