题目链接:
https://ac.nowcoder.com/acm/contest/883/F
题目大意:
给出一个 N ∗ N N*N N∗N矩阵,求其面积最大的子矩阵。子矩阵满足:最大值与最小值之差小于等于给定的数 M M M。
解题思路:
此题
N
<
=
500
N<=500
N<=500,强行暴力的话会写出
O
(
N
6
)
O(N^6)
O(N6)的假算法。题目里提示算法复杂度可能是
O
(
N
3
)
O(N^3)
O(N3)。
不难想到遍历左边界时遍历右边界,这样可以得到一个竖条状区域,然后用双指针从上到下扫一遍,为了保证查询条状区域的宽中的最值不超时,需要用ST表预处理N行,这样复杂度是
O
(
N
2
l
o
g
N
+
N
3
)
O(N^2logN+N^3)
O(N2logN+N3)。
然而貌似因为测试组数太多,常数太大,交上去发现超时了。。。
左右边界似乎是必须要遍历的,只能在条状区域的答案求解上做优化。如果舍弃掉ST表,也就是放弃查询宽的最值,又该如何在仅剩的
O
(
N
)
O(N)
O(N)复杂度里求出答案呢?
双指针+ST表的查询已经够快了,ST表的预处理都不能接受说明不应该造出条状区域单独求解。换句话说,应该在前面的
O
(
N
2
)
O(N^2)
O(N2)中处理些什么。这就离正确答案很近了。
为了绕开直接求宽的最值,我们应该考虑在遍历右边界时就算出当前最好的答案。也就是要用
O
(
N
)
O(N)
O(N)的代价算出某一列的答案,并且这一列的最值性质能影响到后续的列。使用单调队列就能做到这两点,详情见代码(实现时是枚举上下边界)。
AC代码:
#include <stdio.h>
#include <algorithm>
#include <queue>
#include <math.h>
#include <memory.h>
#include <ctype.h>
using namespace std;
#define INF 0x7FFFFFFF
inline int read(){
int res = 0, w = 0; char ch = 0;
while(!isdigit(ch)){
w |= ch == '-', ch = getchar();
}
while(isdigit(ch)){
res = (res << 3) + (res << 1) + (ch ^ 48);
ch = getchar();
}
return w ? -res : res;
}
int mp[505][505],n,m;
int mx[505],mi[505],miq[505],mxq[505];
int main()
{
int T;
T = read();
while(T--)
{
n = read();m = read();
memset(mp,0,sizeof(mp));
for (int i=1;i<=n;i++)
for (int j=1;j<=n;j++)
mp[i][j] = read();
int ans=0;
int mxh,mxt,mih,mit,l,r,len;
for(int x1=1;x1<=n;++x1)//枚举上边界
{
for(int i=1;i<=n;++i)
mi[i]=1e5+10,mx[i]=0;
for(int x2=x1;x2<=n;++x2)//枚举下边界
{
l=1;mih=1;mit=0;mxh=1;mxt=0;
for(int i=1;i<=n;++i)//枚举右边界
{
//注意,这里放心地只对mp[x2][]这一行做单调队列,是因为先枚举上边界后枚举下边界,每行x2都会被看一次,上一行的结果等到循环到下一行时并不会清空,所以[1...i]的最值其实是考虑了x1到x2的所有行的[1...i]列的最值
mi[i]=min(mi[i],mp[x2][i]);
mx[i]=max(mx[i],mp[x2][i]);
while(mih<=mit && mi[i]<=mi[miq[mit]])
--mit;//mi[i]入队时弹出所有比它大的
miq[++mit]=i;
while(mxh<=mxt && mx[i]>=mx[mxq[mxt]])
--mxt;
mxq[++mxt]=i;
while(mih<=mit && mxh<=mxt && mx[mxq[mxh]]-mi[miq[mih]]>m)
{
l=min(mxq[mxh]+1,miq[mih]+1);
while(mih<=mit && miq[mih]<l)
++mih;
while(mxh<=mxt && mxq[mxh]<l)
++mxh;
}
ans=max(ans,(x2-x1+1)*(i-l+1));
}
}
}
printf("%d\n",ans);
}
}