十五届华中科技大学程序设计邀请赛现场赛 I题 单调队列

题意:给你一个n*m的矩阵,让你找到一个最大的L值,满足边长为L的正方形子矩阵中最大最小值差值小于等于G

解析:一个显然的做法是使用二维数据结构,然而二维线段树和二维树状数组容易写炸,二维RMQ被卡了空间,于是只能选择滚动数组dp或者单调队列进行求解。

由于笔者很少利用单调队列解题,正好通过这道题补一补单调队列的知识。

对于单调队列,我们这样子来定义:

  • 1、维护区间最值
  • 2、去除冗杂状态 对于区间中的两个元素a[i],a[j](假设现在在求最大值)
    若 j>i且a[j]>=a[i] ,a[j]比a[i]还大而且还在后面(目前a[j]留在队列肯定比a[i]有用)
  • 3、保持队列单调,最大值是单调递减序列,最小值反之
  • 4、最优选择在队首

单调队列能够在O(n)的复杂度内处理出所有区间长度为m的区间最大/最小值

那么再使用悬线法思想,先用单调队列把每一列的状态压缩为一行,再对这一行使用单调队列即可处理出m*m的矩阵的最大最小值,然后我们再二分答案,对每一次的mid在O(n*m)的复杂度内即可判断是否符合条件,整体复杂度O(nmlog(min(n,m))

代码如下:

#include<bits/stdc++.h>
using namespace std;
int n,m,G;
int a[505][505];
int dmax[505][505];
int dmin[505][505];
int rmax[505][505];
int rmin[505][505];
int work(int L)
{
    deque<pair<int,int> > mmax;
    deque<pair<int,int> > mmin;
    for(int j=1;j<=m;j++)
    {
        mmax.clear(),mmin.clear();
        for(int i=1;i<L;i++)
        {
            while(!mmin.empty()&&mmin.back().first>=a[i][j]) mmin.pop_back();
            mmin.push_back(make_pair(a[i][j],i));
 
            while(!mmax.empty()&&mmax.back().first<=a[i][j]) mmax.pop_back();
            mmax.push_back(make_pair(a[i][j],i));
        }
        for(int i=L;i<=n;i++)
        {
            while(!mmin.empty()&&mmin.back().first>=a[i][j]) mmin.pop_back();
            mmin.push_back(make_pair(a[i][j],i));
            while(mmin.front().second+L<=i) mmin.pop_front();
            dmin[i-L+1][j]=mmin.front().first;
 
            while(!mmax.empty()&&mmax.back().first<=a[i][j]) mmax.pop_back();
            mmax.push_back(make_pair(a[i][j],i));
            while(mmax.front().second+L<=i) mmax.pop_front();
            dmax[i-L+1][j]=mmax.front().first;
        }
    }
    for(int i=1;i<=n;i++)
    {
        mmax.clear(),mmin.clear();
        for(int j=1;j<L;j++)
        {
            while(!mmin.empty()&&mmin.back().first>=dmin[i][j]) mmin.pop_back();
            mmin.push_back(make_pair(dmin[i][j],j));
 
            while(!mmax.empty()&&mmax.back().first<=dmax[i][j]) mmax.pop_back();
            mmax.push_back(make_pair(dmax[i][j],j));
        }
        for(int j=L;j<=m;j++)
        {
            while(!mmin.empty()&&mmin.back().first>=dmin[i][j]) mmin.pop_back();
            mmin.push_back(make_pair(dmin[i][j],j));
            while(mmin.front().second+L<=j) mmin.pop_front();
            rmin[i][j-L+1]=mmin.front().first;
 
            while(!mmax.empty()&&mmax.back().first<=dmax[i][j]) mmax.pop_back();
            mmax.push_back(make_pair(dmax[i][j],j));
            while(mmax.front().second+L<=j) mmax.pop_front();
            rmax[i][j-L+1]=mmax.front().first;
        }
    }
    for(int i=1;i<=n-L+1;i++)
        for(int j=1;j<=m-L+1;j++)
            if(rmax[i][j]-rmin[i][j]<=G)
                return 1;
    return 0;
}
int main()
{
    scanf("%d%d%d",&n,&m,&G);
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++)
            scanf("%d",&a[i][j]);
    int l=1,r=min(n,m);
    int ans=1;
    while(l<=r)
    {
        int mid=l+r>>1;
        if(work(mid))
        {
            ans=mid;
            l=mid+1;
        }
        else r=mid-1;
    }
    printf("%d\n",ans);
    return 0;
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值