洛谷 2216 题解

题意简述

二维滑动窗口。给定一个 a × b a\times b a×b的矩阵,和一个大小为 k × k k\times k k×k的滑动窗口,求这个窗口在滑动过程中所看到的(最大值-最小值)最小能到多少。

数据

输入
5 4 2
1 2 5 6
0 17 16 0
16 17 2 1
2 10 2 1
1 2 2 2
输出
1

思路

理论上是珂以跑两维单调队列的,当然也珂以跑二维 S T ST ST表。由于我不会二维,所以我只能 S T ST ST表套单调队列来写(群众:wdnmd您是沙雕么???)
好的我们来看看我的瞎搞做法。。。
设身处地的想一下,如果你不会二维的单调队列或 S T ST ST表,你会怎么办?
首先要先枚举区间左端点 i i i,然后把所有行上从 i i i i + x − 1 i+x-1 i+x1之间的最大/最小求出来(所以这告诉我们,我们对每一行都要写一个 S T ST ST表)。如下图:
blog1.png
蓝色部分就是我们按行处理的最大/最小值。我们把每一行的最大/最小值放到数组里面,然后就变成一维的问题了。如下图:
blog2.png
对保存行最大值那个数组(设为 t m p x tmpx tmpx),我们跑一遍维护最大值的单调队列。对保存行最小值的那个数组(设为 t m p n tmpn tmpn),我们跑一遍维护最小值的单调队列,取两个队首相减的最小值即可求出答案。
代码(有很多注释):

#include<bits/stdc++.h>
#define N 1010
#define LogN 11
#define K 110
using namespace std;
int mp[N][N];
int n,m,x;
void Input()//读入没什么好说的。。。
{
    scanf("%d%d%d",&n,&m,&x);
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=m;j++)
        {
            scanf("%d",&mp[i][j]);
        }
    }
}

//以下变量名中,以x结尾的是求最大用的,以n结尾的是求最小用的
//比如stx就是最大值的st表,stn就是最小值的st表
int stx[N][N][LogN],stn[N][N][LogN],lg[N];
int tmpx[N],tmpn[N];
void BuildST()
{
    for(int i=1;i<=n;i++)//预处理每个数的log2值,常数小一点
    {
        lg[i]=lg[i-1]+((1<<(lg[i-1]+1))==i);
    }

    for(int i=1;i<=n;i++)//ST表的边界(这个少了药丸啊。。。)
    {
        for(int j=1;j<=m;j++)
        {
            stn[i][j][0]=stx[i][j][0]=mp[i][j];
        }
    }
    for(int i=1;i<=n;i++)//转移
    {
        for(int k=1;(1<<k)<=m;k++)
        {
            for(int j=1;j+(1<<(k-1))<=m;j++)
            {
                stx[i][j][k]=max(stx[i][j][k-1],stx[i][j+(1<<(k-1))][k-1]);
                stn[i][j][k]=min(stn[i][j][k-1],stn[i][j+(1<<(k-1))][k-1]);
            }
        }
    }
}
int Query(int st[][N][LogN],int i,int l,int r)//求最大/最小
//第一个参数是一个指针,是stn就表示询问最小,是stx就表示询问最大
{
    int lc=lg[x];//r-l+1==x
    if (st==stx)//询问最大
    {
        return max(st[i][l][lc],st[i][r-(1<<lc)+1][lc]);
    }
    else//询问最小
    {
        return min(st[i][l][lc],st[i][r-(1<<lc)+1][lc]);
    }
}
int Qx[N],hx,tx;int Qn[N],hn,tn;
void Solve()
{
    int totans=0x3f3f3f3f;//总共的答案
    for(int i=1;i+x-1<=m;i++)
    {
        memset(tmpn,0,sizeof(tmpn));
        memset(tmpx,0,sizeof(tmpx));
        for(int j=1;j<=n;j++)//上图的蓝色部分(降维打击)
        {
            tmpx[j]=Query(stx,j,i,i+x-1);
            tmpn[j]=Query(stn,j,i,i+x-1);
        }

		//以下是洛谷1886滑动窗口的代码
        hx=tx=hn=tn=1;
        for(int j=1;j<=x;j++)
        {
            while(hx<tx and tmpx[Qx[tx-1]]<=tmpx[j]) tx--;
            Qx[tx]=j,tx++;
            while(hn<tn and tmpn[Qn[tn-1]]>=tmpn[j]) tn--;
            Qn[tn]=j,tn++;
        }

        int ans=tmpx[Qx[hx]]-tmpn[Qn[hn]];
        for(int j=x+1;j<=n;j++)
        {
            while(hx<tx and tmpx[Qx[tx-1]]<=tmpx[j]) tx--;
            Qx[tx]=j,tx++;
            while(hn<tn and tmpn[Qn[tn-1]]>=tmpn[j]) tn--;
            Qn[tn]=j,tn++;

            if (j-Qx[hx]+1>x)
            {
                ++hx;
            }
            if (j-Qn[hn]+1>x)
            {
                ++hn;
            }
            //ans记录以i为左端点的蓝色部分最大-最小的最小值
            ans=min(ans,tmpx[Qx[hx]]-tmpn[Qn[hn]]);
        }
        totans=min(totans,ans);//计算到总答案
    }
    printf("%d\n",totans);
}

main()
{
    Input();
    BuildST();
    Solve();
    return 0;
}

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值