AcWing 1091. 理想的正方形


题目传送门

一维滑动窗口模板题

一、解题思路

二维滑动窗口模板题

①和②的步骤都是用单调队列来进行优化
核心思路

一维单调队列求解区间的最值

void get_min(int a[], int b[], int tot)
{
    int hh = 0, tt = -1;
    for (int i = 1; i <= tot; i ++ )
    {
        if (hh <= tt && q[hh] < i - k + 1) hh ++ ;
        while (hh <= tt && a[q[tt]] >= a[i]) tt -- ;
        q[ ++ tt] = 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 ++ )
    {
        if (hh <= tt && q[hh] <= i - k) hh ++ ;
        while (hh <= tt && a[q[tt]] <= a[i]) tt -- ;

        q[ ++ tt] = i;
        //因为第i个元素也可能包含,所以最后再处理
        b[i] = a[q[hh]];
    }
}

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

//分别找到每一行的每个[j - n - 1,j]区间的最小值和最大值
    for(int i = 1;i <= n;++i){
        get_min(w[i],row_min[i],m);
        get_max(w[i],row_max[i],m);
    }

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

//接着找到每列的最大值和最小值(直接从第n列开始,即表示第一个n * n的矩阵的每行内的最大元素)
    for(int j = k;j <= m;++j){ 
        //枚举列,因为第一个区间内的最大值或者最小值开始是存放在下标为n

        //枚举行,计算当前这一列中所有横向区间的最值复制给ans
        for(int i = 1;i <= n;++i) ans[i] = row_min[i][j];
        get_min(ans,col_min,n);   //最多有n行


        for(int i = 1;i <= n;++i) ans[i] = row_max[i][j];
        get_max(ans,col_max,n);
}

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

for(int i = k;i <= n;++i){
    res = min(res,col_max[i] - col_min[i]);
}

二、完整代码

#include 

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];

void get_min(int a[], int b[], int tot) {
    int hh = 0, tt = 0;
    for (int i = 1; i <= tot; i++) {
        if (q[hh] <= i - k) hh++;
        while (hh <= tt && a[q[tt]] >= a[i]) tt--;
        q[++tt] = i;
        //记录最新的队头元素值
        b[i] = a[q[hh]];
    }
}

void get_max(int a[], int b[], int tot) {
    int hh = 0, tt = 0;
    for (int i = 1; i <= tot; i++) {
        if (q[hh] <= i - k) hh++;
        while (hh <= tt && a[q[tt]] <= a[i]) tt--;
        q[++tt] = 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];

    //计算每一行的最小值和最大值,分别保存到row_min和row_max数组中
    for (int i = 1; i <= n; i++) {
        get_min(w[i], row_min[i], m);
        get_max(w[i], row_max[i], m);
    }

    int res = INF;
    int a[N], b[N], c[N];
    //遍历每一个有意义的列
    for (int i = k; i <= m; i++) {
        //将此列每一行的数据拷贝到a数组中,可以再次调用二维模板进行最小值计算
        for (int j = 1; j <= n; j++) a[j] = row_min[j][i];
        get_min(a, b, n);
        //将此列每一行的数据拷贝到a数组中,可以再次调用二维模板进行最大值计算
        for (int j = 1; j <= n; j++) a[j] = row_max[j][i];
        get_max(a, c, n);
        //找出min(区域最大值-区域最小值)
        for (int j = k; j <= n; j++) res = min(res, c[j] - b[j]);
    }
    //输出
    printf("%d\n", res);
    return 0;
}