bzoj1047 单调队列

这篇博客介绍如何使用单调队列解决一个矩阵问题:在a*b的矩阵中找到一个n*n的正方形区域,使区域内最大值和最小值之差最小。通过示例输入和输出解释了问题的要求,并阐述了单调队列的概念和优化动态规划的方法。最后,指出此题是二维单调队列的应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Description

  有一个a*b的整数组成的矩阵,现请你从中找出一个n*n的正方形区域,使得该区域所有数中的最大值和最小值
的差最小。

Input

  第一行为3个整数,分别表示a,b,n的值第二行至第a+1行每行为b个非负整数,表示矩阵中相应位置上的数。每
行相邻两数之间用一空格分隔。
100%的数据2<=a,b<=1000,n<=a,n<=b,n<=1000
Output

  仅一个整数,为a*b矩阵中所有“n*n正方形区域中的最大整数和最小整数的差值”的最小值。

Sample Input

5 4 2

1 2 5 6

0 17 16 0

16 17 2 1

2 10 2 1

1 2 2 2
Sample Output

1
还在为学不会单调队列而苦恼么?
还在看一篇又一篇写的很诡异的博客么?
我们已经为你准备了一大堆习题来教你单调队列,我们保证你绝对做到吐
单调队列单调队列顾名思义队列里面的元素是有单调性的要么递增要么递减
我们考虑,一个靠后的点虽然可能在数值上不占优势但是它可以更新的更远,但是如果他在数值上也占优势的话那么前面的点不就很没用了么,那么就让他们全部出队。当队列滑动的过程中要把不合题意的点出队。这就是单调队列优化dp
那么这个题也就比较明确了。
那么此题其实是一个非常裸的二维单调队列。
用max【i】【j】表示以(i,j)为右下角的最大值
那么我们先求出每一行的情况然后求出前i行的情况

#include<cstdio>
#include<cstring>
#include<utility>
#include<algorithm>
#include<iostream>
#include<queue>
#include<stack>
#include<cmath>
#include<cstdlib>
#include<ctime>
using namespace std;
inline int read()
{
    char ch='*';
    int f=1;
    while(!isdigit(ch=getchar())) if(ch=='-') f=-1;
    int num=ch-'0';
    while(isdigit(ch=getchar())) num=num*10+ch-'0';
    return num*f;
}
const int N=1005;
int mp[N][N],h[N],t[N],Q[N],q[N][N];
int mx[N][N],mn[N][N];
int s,e,a,b,n;





void getmax()
{
    for(register int i=1;i<=b;++i)
        h[i]=1,t[i]=0;
    for(register int i=1;i<=a;++i){
        for(register int j=1;j<=b;++j)
        {
            while(h[j]<=t[j]&&i-q[j][h[j]]>=n) ++h[j];
            while(h[j]<=t[j]&&mp[q[j][t[j]]][j]<=mp[i][j]) --t[j];
            q[j][++t[j]]=i;
        }
        e=0,s=1;
        for(register int j=1;j<=b;j++){
            while(s<=e&&j-Q[s]>=n) ++s;
            while(s<=e&&mp[q[Q[e]][h[Q[e]]]][Q[e]]<=mp[q[j][h[j]]][j]) e--;
            Q[++e]=j;
            mx[i][j]=mp[q[Q[s]][h[Q[s]]]][Q[s]];
        }
    }
    return ;
}
void getmin()
{
    for(register int i=1;i<=b;++i)
        h[i]=1,t[i]=0;
    for(register int i=1;i<=a;++i){
        for(register int j=1;j<=b;++j)
        {
            while(h[j]<=t[j]&&i-q[j][h[j]]>=n) ++h[j];
            while(h[j]<=t[j]&&mp[q[j][t[j]]][j]>=mp[i][j]) --t[j];
            q[j][++t[j]]=i;
        }
        e=0,s=1;
        for(register int j=1;j<=b;j++){
            while(s<=e&&j-Q[s]>=n) ++s;
            while(s<=e&&mp[q[Q[e]][h[Q[e]]]][Q[e]]>=mp[q[j][h[j]]][j]) e--;
            Q[++e]=j;
            mn[i][j]=mp[q[Q[s]][h[Q[s]]]][Q[s]];
        }
    }
    return ;
}
int main()

{
    a=read();b=read();n=read();
    for(register int i=1;i<=a;i++)
        for(register int j=1;j<=b;j++)
            mp[i][j]=read();
    getmax();
    getmin();
    int ans=0x7fffffff;
    for(int i=n;i<=a;i++)
        for(register int j=n;j<=b;j++)
            ans=min(ans,mx[i][j]-mn[i][j]);
    printf("%d\n",ans);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值