理想正方形 题解

先言

若不是看了题解,我倒还真不认为是单调队列优化 \(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 ;
}
posted @ 2021-03-31 11:38  SkyFairy  阅读(113)  评论(2编辑  收藏  举报