• AcWing 1091. 理想的正方形


    \(AcWing\) \(1091\). 理想的正方形

    题目传送门

    一维滑动窗口模板题 \(AcWing\) \(154\). 滑动窗口

    一、题目大意

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

    输入格式
    第一行为三个整数,分别表示 \(a,b,n\) 的值;

    第二行至第 \(a+1\) 行每行为 \(b\) 个非负整数,表示矩阵中相应位置上的数。

    输出格式
    输出仅一个整数,为 \(a×b\) 矩阵中所有 \(n×n\) 正方形区域中的最大整数和最小整数的差值的最小值

    二、解题思路

    二维滑动窗口模板题

    步骤\(1\):枚举每行所有长度为\(k\)的区间的最值,保存在区间的右端点中

    步骤\(2\).枚举长度为\(k\)的同一列的右端点,得到最值,保存在区间的下端点中

    步骤\(3\).计算这一列的所有最大值和最小值的差值,更新结果

    三、暴力版本

    #include <bits/stdc++.h>
    
    using namespace std;
    const int N = 1010;
    const int INF = 0x3f3f3f3f;
    
    int n;             // n行
    int m;             // m列 
    int k;             //区间长度为k
    int w[N][N];       //原矩阵
    
    //暴力法,TLE
    //通过了 4/10个数据
    
    /**
     * 1、这个模板函数处理的是一维数组a[],保存的是一维数组b[]
     * 2、之所以使用一维数组,其实本质上是把二维数组折开每一行,当作一维数组进行处理
     * 3、C++函数参数中传递一维数组,是不知道数组长度的,必须同时传输col,表示a[],b[]一共多少列
     * 4、这个函数用句人话描述就是:把一个一维数组的长度为K区间内的最小值都算出来,放到row_min这个数组中去。比如:k=3,row_min[5]=2,就是表示原数组[3,4,5]下标内,最小值是2
     */
    void get_min(int a[], int b[], int tot) {
        for (int i = 1; i <= tot; i++) { //枚举每一列
            b[i] = INF;
            //找出每个数字包含[自己+前面]共k个范围内的最小值
            // j的含义:指针,从i开始,向前倒k个 j∈[i-k+1,i],举栗子:i=10,k=2,则应该是[9,10];如果i=1,k=2,则j只能取数值[1],即j>0
            for (int j = i; j > max(i - k, 0); j--) b[i] = min(b[i], a[j]);
        }
    }
    
    void get_max(int a[], int b[], int tot) {
        for (int i = 1; i <= tot; i++) { //枚举每一列
            b[i] = -INF;
            for (int j = i; j > max(i - k, 0); j--) b[i] = max(b[i], a[j]);
        }
    }
    
    int main() {
        cin >> n >> m >> k;
        //读入
        for (int i = 1; i <= n; i++)
            for (int j = 1; j <= m; j++)
                cin >> w[i][j];
    
        /*步骤I:遍历每一行,完成最小值、最大值的向右转储,分别记录到row_min、row_max两个数组中。
        这两个数组,只是一个中间的状态,是为了给步骤II“竖着计算k个范围内的极大极小值”提供垫脚石的
        */
        int row_min[N][N]; //最小值
        int row_max[N][N]; //最大值
        for (int i = 1; i <= n; i++) {
            get_min(w[i], row_min[i], m); //填充每一行的,k个长度的区间内最小值 保存到row_min数组中, 注意:这并不是指某一个最小值,而是从k~m的所有长度够k个长度的区间极小值
            get_max(w[i], row_max[i], m); //填充每一行的,k个长度的区间内最大值 保存到row_max数组中, 注意:这并不是指某一个最大值,而是从k~m的所有长度够k个长度的区间极大值
        }
    
        int t[N]; //列转行,用到的临时中转数组
        int a[N]; //最小值数组
        int b[N]; //最大值数组
    
        /*
        步骤II:将竖向的区间极值向右下角归并
          (1)、依托row_min,对每一列进行列转行,保存为t
          (2)、利用get_min 将t数组再次存储为a数组
          (3)、此a数组,就是左归右,上归下的,边长为k的矩形中的最小值 
    
          (4)、依托row_max,对每一列进行列转行,保存为t
          (5)、利用get_max 将t数组再次存储为b数组
          (6)、此b数组,就是左归右,上归下的,边长为k的矩形中的最大值 
        */
        int res = INF; //预求最小,先设最大
    
        for (int j = k; j <= m; j++) { //捋着列来
    
            for (int i = 1; i <= n; i++) t[i] = row_min[i][j]; //同一列的每一行,抄出来放到临时数组q中
            get_min(t, a, n);                                  //对q这个临时数组,进行求k个范围内的最小值,将结果保存到a数组中
    
            for (int i = 1; i <= n; i++) t[i] = row_max[i][j]; //同一列的每一行,抄出来放到临时数组q中
            get_max(t, b, n);                                  //对q这个临时数组,进行求k个范围内的最大值,将结果保存到b数组中
    
            //区域最大值-区域最小值
            for (int i = k; i <= n; i++) res = min(res, b[i] - a[i]);
        }
        //输出
        printf("%d\n", res);
        return 0;
    }
    
    

    四、单调队列优化

    #include <bits/stdc++.h>
    
    using namespace std;
    const int N = 1010;
    const int INF = 0x3f3f3f3f;
    
    int n, m, k;
    int w[N][N];
    int row_min[N][N], row_max[N][N];
    int q[N];
    
    //先想清楚get_min的意义:对于每个一维数组中的数字,找出包括它自己在内,长度最长为k的范围内,最小值是多少
    //这是一个典型的单调队列问题,窗口长度为k,包含自己在内
    
    void get_min(int a[], int b[], int tot) {
        int hh = 0, tt = -1; //和前缀和相关的,才会有哨兵。这里和前缀和没关系,不用加入哨兵。
        for (int i = 1; i <= tot; i++) {
            //举栗子:比如i=5,k=3,则窗口范围是[3,4,5],也就是最远的队头元素下标是i-k+1,再比它小就不行了
            while (hh <= tt && q[hh] < i - k + 1) hh++;
            while (hh <= tt && a[q[tt]] >= a[i]) tt--; //赶走比我老,但值还比我大的那些老家伙
            q[++tt] = i;
            //此处需要包括i本身,所以在添加到队列后进行计算
            b[i] = a[q[hh]];
        }
    }
    
    void get_max(int a[], int b[], int tot) {
        int hh = 0, tt = -1;
        //和前缀和相关的,才会有哨兵。这里和前缀和没关系,不用加入哨兵。
        for (int i = 1; i <= tot; i++) {
            while (hh <= tt && q[hh] < i - k + 1) hh++;
            while (hh <= tt && a[q[tt]] <= a[i]) tt--;
            q[++tt] = i;
            //此处需要包括i本身,所以在添加到队列后进行计算
            b[i] = a[q[hh]];
        }
    }
    
    int main() {
        cin >> n >> m >> k;
        //读入
        for (int i = 1; i <= n; i++)
            for (int j = 1; j <= m; j++)
                cin >> w[i][j];
    
        /*步骤I:遍历每一行,完成最小值、最大值的向右转储,分别记录到row_min、row_max两个数组中。
        这两个数组,只是一个中间的状态,是为了给步骤II“竖着计算k个范围内的极大极小值”提供垫脚石的
        */
        int row_min[N][N]; //最小值
        int row_max[N][N]; //最大值
        for (int i = 1; i <= n; i++) {
            get_min(w[i], row_min[i], m); //填充每一行的,k个长度的区间内最小值 保存到row_min数组中, 注意:这并不是指某一个最小值,而是从k~m的所有长度够k个长度的区间极小值
            get_max(w[i], row_max[i], m); //填充每一行的,k个长度的区间内最大值 保存到row_max数组中, 注意:这并不是指某一个最大值,而是从k~m的所有长度够k个长度的区间极大值
        }
    
        int t[N]; //列转行,用到的临时中转数组
        int a[N]; //最小值数组
        int b[N]; //最大值数组
    
        /*
        步骤II:将竖向的区间极值向右下角归并
          (1)、依托row_min,对每一列进行列转行,保存为t
          (2)、利用get_min 将t数组再次存储为a数组
          (3)、此a数组,就是左归右,上归下的,边长为k的矩形中的最小值 
    
          (4)、依托row_max,对每一列进行列转行,保存为t
          (5)、利用get_max 将t数组再次存储为b数组
          (6)、此b数组,就是左归右,上归下的,边长为k的矩形中的最大值 
        */
        int res = INF; //预求最小,先设最大
    
        for (int j = k; j <= m; j++) { //捋着列来
    
            for (int i = 1; i <= n; i++) t[i] = row_min[i][j]; //同一列的每一行,抄出来放到临时数组t中
            get_min(t, a, n);                                  //对t这个临时数组,进行求k个范围内的最小值,将结果保存到a数组中
    
            for (int i = 1; i <= n; i++) t[i] = row_max[i][j]; //同一列的每一行,抄出来放到临时数组t中
            get_max(t, b, n);                                  //对t这个临时数组,进行求k个范围内的最大值,将结果保存到b数组中
    
            //区域最大值-区域最小值
            for (int i = k; i <= n; i++) res = min(res, b[i] - a[i]);
        }
        //输出
        printf("%d\n", res);
        return 0;
    }
    
  • 相关阅读:
    Leetcode OJ: Rotate List
    Leetcode OJ: Reverse Words in a String
    Effective C++读书笔记
    word改变下划线与文字的距离
    sql 取出表中不重复的值
    Iso文件用utrliso怎么安装
    Spring注入aspectJ切面
    Spring中利用java注解声明切面
    Spring面向切面编程
    spring中部分异常
  • 原文地址:https://www.cnblogs.com/littlehb/p/15817605.html
Copyright © 2020-2023  润新知