理想正方形 题解

先言

若不是看了题解,我倒还真不认为是单调队列优化 (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 ;
}
原文地址:https://www.cnblogs.com/Zmonarch/p/14600731.html