理想正方形 题解
先言
若不是看了题解,我倒还真不认为是单调队列优化 \(DP\) , 关于正解的话,不清楚,但是这道题 \(RMQ\) , 线段树 + \(O_2\) 或者 单调队列优化都可以过,(笔者没有挨个实现)
description
在一个 \(n \times m\) 的矩阵中,找出一个 \(k \times 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\times m \times 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\to n - k + 1\) 的,毕竟是左节点,毕竟在这里看了 \(20min\) 的错误,显然是有发言权的。
枚举整个矩形这是必不可少的,也就是 \(O(n\times m)\) 的复杂度是必然少不了的。我们需要简化掉取最小值和最大值,这里用的是单调对列的优化, 我们用对行和列分别建立单调队列,从而在枚举整个矩形的时候直接给整出来就 \(OK\) 了,这个特别像滑动窗口 。
说明一下代码内的意思 :
带 \(1\) 的表示最大值 ,带 \(2\) 表示最小值
- \(f1_{i,j}\) 表示 第 \(i\) 行,第 $j \to 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\n" , 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\n" , ans) ;
return 0 ;
}