二维单调队列

二维单调队列

(二维)单调队列通常解决区间最值问题 。
比如:

有一个 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[]<ik+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;
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值