二维单调队列
(二维)单调队列通常解决区间最值问题 。
比如:
有一个 a*b 的整数组成的矩阵,现请你从中找出一个 n*n 的正
方形区域,使得该区域所有数中的最大值和最小值的差最小。
先介绍一下单调队列:
问题描述:用一个长度为k的窗在整数数列上移动,求窗里面所包含的数的最大值。
解法一:
很直观的一种解法,那就是从数列的开头,将窗放上去,然后找到这最开始的k个数的最大值,然后窗最后移一个单元,继续找到k个数中的最大值。
解法二:
我们知道,上一种算法有一个地方是重复比较了,就是在找当前的f(i)的时候,i的前面k-1个数其它在算f(i-1)的时候我们就比较过了。那么我们能不能保存上一次的结果呢?当然主要是i的前k-1个数中的最大值了。答案是可以,这就要用到单调递减队列。
单调递减队列是这么一个队列,它的头元素一直是队列当中的最大值,而且队列中的值是按照递减的顺序排列的。我们可以从队列的末尾插入一个元素,可以从队列的两端删除元素。
1.首先看插入元素:为了保证队列的递减性,我们在插入元素v的时候,要将队尾的元素和v比较,如果队尾的元素不大于v,则删除队尾的元素,然后继续将新的队尾的元素与v比较,直到队尾的元素大于v,这个时候我们才将v插入到队尾。
2.队尾的删除刚刚已经说了,那么队首的元素什么时候删除呢?由于我们只需要保存i的前k-1个元素中的最大值,所以当队首的元素的索引或下标小于i-k+1的时候,就说明队首的元素对于求f(i)已经没有意义了,因为它已经不在窗里面了。所以当
index[队首元素]<i−k+1
时,将队首元素删除。
那么问题来了!二维的怎么搞?!
首先只看一层,可以用单调队列搞出来从每个点开始往后 n 个数的极值,用同样的方法将每一行都处理出来,并用数组 w[i][j]记录第 i 行,从第 j 个开始往后 n 个数的最大值,ww[i][j]记录最小值。接下来再来纵看,但看其中一列,也用同样的方法以 w 和 ww 数组为依据来搞单调队列,因为 w 已经将横向长为 n 的最大值存储在左边的 n个点中了,纵向再求出这 n 个点的最大值,就是这 n*n 的矩阵的最大值。最后再枚举一下正方形的左上定点,求出结果。
附最初问题代码:
#include<cstdio>
#include<cstring>
#include<cstdlib>
using namespace std;
const int N=1001;
struct node{
int pos,x;
}max[N],min[N];
int h1,t1,h2,t2;
int a[N][N],w[N][N],ww[N][N];
int main(){
//freopen("square.in","r",stdin);
//freopen("square.out","w",stdout);
int aa,bb,n;
scanf("%d%d%d",&aa,&bb,&n);
for(int i=1;i<=aa;i++)
for(int j=1;j<=bb;j++) scanf("%d",&a[i][j]);
for(int j=1;j<=aa;j++){
h1=h2=1; t1=t2=0;
for(int i=1;i<=bb;i++){
if(h1<=t1 && max[h1].pos<i-n+1) h1++;
if(h2<=t2 && min[h2].pos<i-n+1) h2++;
while(h1<=t1 && max[t1].x<a[j][i]) t1--;
max[++t1].pos=i; max[t1].x=a[j][i];
while(h2<=t2 && min[t2].x>a[j][i]) t2--;
min[++t2].pos=i; min[t2].x=a[j][i];
if(i-n+1>0)
w[j][i-n+1]=max[h1].x,ww[j][i-n+1]=min[h2].x;
}
}
for(int j=1;j<=bb-n+1;j++){
h1=h2=1; t1=t2=0;
for(int i=1;i<=aa;i++){
if(h1<=t1 && max[h1].pos<i-n+1) h1++;
if(h2<=t2 && min[h2].pos<i-n+1) h2++;
while(h1<=t1 && max[t1].x<w[i][j]) t1--;
max[++t1].pos=i; max[t1].x=w[i][j];
while(h2<=t2 && min[t2].x>ww[i][j]) t2--;
min[++t2].pos=i; min[t2].x=ww[i][j];
if(i-n+1>0)
w[i-n+1][j]=max[h1].x,ww[i-n+1][j]=min[h2].x;
}
}
int ans=0x7fffffff;
for(int i=1;i<=aa-n+1;i++)
for(int j=1;j<=bb-n+1;j++)
if(w[i][j]-ww[i][j]<ans) ans=w[i][j]-ww[i][j];
printf("%d\n",ans);
return 0;
}