最近在补全提高课所有题目的题解,宣传一下汇总的地方提高课题解补全计划
题目描述
给定一个 $a \times b$ 的 矩阵 $w$,以及一个 正整数 $n$(其中 $w_{i,j}$ 为第 $i$ 行第 $j$ 列元素的 价值)
求 矩阵 $w$ 中,边长 为 $n$,且元素 最小最大值的差值最小 的 子矩阵,输出该 差值
分析
看到 区间最值 问题,第一反应是 预处理 st表,做一遍 二维RMQ
RMQ 本身是用于处理 多组询问 的,而本题则是要把所有 恒定边长 的 区间最值 找出来
介于只用维护 恒定边长,因此没有必要上 RMQ,但是 RMQ 的 思想 可以继承过来
考虑求一个 二维 的 最值问题,我们可以把它转化为一维 的 最值问题:
图中 红色阴影 格为该 行中 的 最值元素,绿色背影色 格为整个矩阵的 最值元素
由常见公式 $\max\Big\{a,b,c,d\Big\} = \max\Big\{\max(a,b), \max(c,d)\Big\}$ 可知:
我们可以预处理出每 列 的行中 最值,再对该列求一个 最值,就是该 子矩阵 的 最值
该 降维手段,使得一个 二维滑动窗口问题 变成了 n行+n列 的 一维滑动窗口问题
而 一维滑动窗口 我们可太熟悉了,直接套 单调队列 的模板即可
所以这题和 动态规划 有什么关系吗 QAQ
Code
#include <iostream>
using namespace std;
const int N = 1010;
int n, m, k, res = 1e9;
int w[N][N], minv[N][N], maxv[N][N], que[N];
int a[N], b[N], c[N], d[N];
void get_max(int a[], int f[], int m)
{
int hh = 0, tt = -1;
for (int i = 1; i <= m; i ++ )
{
while (hh <= tt && i - que[hh] >= k) hh ++ ;
while (hh <= tt && a[i] >= a[que[tt]]) tt -- ;
que[ ++ tt] = i;
f[i] = a[que[hh]];
}
}
void get_min(int a[], int f[], int m)
{
int hh = 0, tt = -1;
for (int i = 1; i <= m; i ++ )
{
while (hh <= tt && i - que[hh] >= k) hh ++ ;
while (hh <= tt && a[i] <= a[que[tt]]) tt -- ;
que[ ++ tt] = i;
f[i] = a[que[hh]];
}
}
int main()
{
scanf("%d%d%d", &n, &m, &k);
for (int i = 1; i <= n; i ++ )
for (int j = 1; j <= m; j ++ )
scanf("%d", &w[i][j]);
for (int i = 1; i <= n; i ++ )
{
get_min(w[i], minv[i], m);
get_max(w[i], maxv[i], m);
}
for (int i = k; i <= m; i ++ )
{
for (int j = 1; j <= n; j ++ )
{
a[j] = maxv[j][i];
b[j] = minv[j][i];
}
get_max(a, c, n);
get_min(b, d, n);
for (int j = k; j <= n; j ++ ) res = min(res, c[j] - d[j]);
}
printf("%d\n", res);
return 0;
}
这个图好形象呀!
我试了下deque,用deque实现是会被卡时间嘛?好像TLE了
你的代码+ (o)3优化就过了
巨巨,我想请教一下。
for (int i = 1; i <= m; i ++ ) { while (hh <= tt && i - que[hh] >= k) hh ++ ; while (hh <= tt && a[i] >= a[que[tt]]) tt -- ; que[ ++ tt] = i; f[i] = a[que[hh]]; }
这个get_max里面,把最后一行移到第二行之前,就报错呀?
$j$ 的范围应该是:$0 \le i-j \lt k$,所以求 $i$ 的最值时,要先把 $i$ 滑进当前的滑动窗口
具体一点来理解的话,计算以第 $i$ 哥元素为 右端点 的最大值,要算上第 $i$ 个元素
这个分析也适用所有的 滑动窗口模型 问题
如果 $1 \le i - j$ 就要先算,再加
如果 $0 \le i - j$ 就要先加,再算
感谢大佬指点!!!十分通透
题解很清晰,彩铅大佬tql!!!
谢谢大佬的夸奖 w
我不太理解关于tt=0和tt=-1有什么差别
tt=0:表示提前入队列了一个哨兵,此种情况一般与前缀和共同使用,因为计算s[9]-s[0],就是获取1~9的前缀和,此时提前入队列一个哨兵,保证了所有数据的处理逻辑一致,否则就会造成对于第1个数据,它的处理逻辑与其它的不一致.
tt=-1:这是基础课中队列的标准姿势,就是普通的队列,没有加入哨兵。因为本题没有涉及到前缀和概念,不需要什么哨兵,而且,通过观察法,你就能知道,主体代码在没有初始入哨兵时才是正确的。
哈哈哈,牛逼大佬
二维降到一维的过程有详细点的文章吗
佬,如果用minv[i][j]表示从(1,1)到(i,j)的最小值,行从k开始才是对的呢,从1开始就不对了呢,我是用一个二维数组minv[i][j]存储最大值,maxv[i][j]存储最大值,计算res是把单独提出了了。我的代码:
https://pastebin.ubuntu.com/p/2XChXt3bSq/
第51行标注了我的问题
注释的那一行改成 $1$ 也能过,不影响答案的枚举,但是 $1\sim k-1$ 的迭代都是不合法答案
可以看一下上面有关二维滑动窗口化一维的思路
我们是把第一 列 合法的子窗口的 行中 最值存储到第 $k$ 列的单独元素中去
如果去迭代 $1\sim k-1$ 的列,是没有意义的,他们的实际意义是 $1\sim k-1$ 长的子矩阵
计算他们的时候 滑动窗口 未满
如果维护的 二维滑动窗口 要求是长宽不超过 $k$ 的话,那样才是正解
明白了,谢谢大佬解答!
巨巨这么晚还在写题解,我又有什么资格睡觉!
冲冲冲