洛谷 2216 题解

题意简述

二维滑动窗口。给定一个a×ba\times ba×b的矩阵,和一个大小为k×kk\times kk×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

思路

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

#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;
}

P1008是“三连击[NOIP 1998 普及组]”问题,以下为几种题解思路及代码: ### 函数型题解 定义一个函数拆分组合出来的每一位数,通过循环找出满足条件的数,判断每个数的每一位是否都被使用且只用1次。 ```cpp #include<iostream> using namespace std; int gw; //个位 int d[9];//用来计数,因为一个数的个位只可能是1,2,3...到9,所以只要9位 int cf(int x){//我们定义一个函数,拆分我们组合出来的每一位数,所以它叫拆分√ while(x!=0){ gw=x%10; x=(x-gw)/10; //其实完全可以x/10,为了理解方便,我们将x的个位减为0,然后/10把0削去,不断将当期的数拆分 d[gw]++;//计数器加1 } } int main(){ int a,b,c; for (int i=1;i<=9;i++) for (int j=1;j<=9;j++) for (int k=1;k<=9;k++){ a=i*100+j*10+k; b=a*2; c=a*3; cf(a);//将创造出的满足条件的数扔到函数中拆分 cf(b); cf(c); if(d[1]==1&&d[2]==1&&d[3]==1&&d[4]==1&&d[5]==1&&d[6]==1&&d[7]==1&&d[8]==1&&d[9]==1){//如果这3个创造出来的数满足每一位都被使用且只用1次,输出 cout<<a<<" "<<b<<" "<<c<<endl; } for (int e=1;e<=9;e++){//判断之后将计数的数组置为0,方便下次使用,不然你一个数都不会输出 d[e]=0;//其实可以用memset重置,但是懒得修改了。不了解memset的可以百度一下 } } return 0;//华华丽丽的结束 } ``` 该代码通过三重循环生成可能的三位数 `a`,然后计算 `b = a * 2` 和 `c = a * 3`,再调用 `cf` 函数拆分每个数的每一位并计数。若 `1 - 9` 每个数字都只出现一次,则输出这三个数,最后将计数器数组重置 [^1]。 ### 另一种循环题解 用循环中的 `i` 表示 `x`,通过限定 `i` 的范围来减少不必要的计算。 ```cpp #include<bits/stdc++.h> using namespace std; int main(){ for(int i=123;i<=327;i++){ int j=i*2,k=i*3; // 后续可添加判断每个数字是否只出现一次的代码 } return 0; } ``` 此代码通过 `for` 循环遍历 `123` 到 `327` 的数作为 `i`,计算 `j = i * 2` 和 `k = i * 3`,后续需要添加判断每个数字是否只出现一次的逻辑 [^5]。 ### 样例及输出要求 输出若干行,每行 3 个数字,按照每行第 1 个数字升序排列。例如样例输出有 `192 384 576` 等 [^3]。 ### 其他解示例 如 `x = 219`,`y = 438`,`z = 657` 也是一组解。将它们各位数字合并后排序得到 `123456789`,满足每个数字只出现一次的条件 [^2]。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值