问题描述
输入格式
辎出格式
输出一个整数. 分别表示小明选择的子矩阵的最大面积。
样例输入
3 4
2 0 7 9
0 6 9 7
8 4 6 4
8
样例输出
6
样例说明
满足稳定度不大于 8 的且面积最大的子矩阵总共有三个, 他们的面积都是 6 (粗体表示子矩阵元素)
2 0 7 9
0 6 9 7
8 4 6 4
2 0 7 9
0 6 9 7
8 4 6 4
2 0 7 9
0 6 9 7
8 4 6 4
评测用例规模与约定
对于所有评测用例, 0≤0≤ 矩阵元素值, limit ≤105≤105 。
运行限制
最大运行时间:5s
最大运行内存: 512M
总通过次数: 104 | 总提交次数: 205 | 通过率: 50.7%
难度: 中等 标签: 2022, 省赛
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.LinkedList;
import java.util.Queue;
import java.util.Scanner;
public class ok最大子矩阵 {
/*数据规模 1e5 n*n*m*m=1e10
* 第一点:矩阵的遍历
* for i 1:n
* for j 1:n
* for k 1:m
* for m 1:m
* m的个数明显大于n,遍历的时候,我先考虑,只遍历n,也就是只确定子矩阵的上下两个底所在的位置
* for i 1:n
* for j 1:n
* 转化为求满足条件的最大值---->二分---->二分的是什么:子矩阵的面积=(j-i+1)*l 二分的是子矩阵的长度
* 判断在子矩阵长度为l的情况下是否满足要求---->check(mid)
* 在求面积为area对应的子矩阵的过程中我们用到了滑动窗口
* 滑动窗口要求什么----->f(m)是否不大于limit
*
* 1.滑动窗口
* 在窗口滑动的同时记录某一个东西:在滑动的过程进行记录,离开窗口的进行删除,进入窗口的进行添加 删除和添加都是O(1) O(n)
* 2.单调队列实现滑动窗口----->需要从二维压缩到一维
* 预处理 记录矩阵的最大值与最小值
* 3.求矩阵的最大值和最小值
* 处理矩阵的每一列---->在某一个位置记录的是该位置所在列前面的所有数据的最小值和最大值,即该列从第0行开始该位置所在行的最大值和最小值
* max[i][j][k]
*
* 二分 滑动窗口 有模板
* 思维点 2.需要把二维转化为一维<-----滑动窗口
* 1.只遍历n,只对n进行暴力遍历 <-----n的数据规模很小
* 3.压缩的过程保留关键数据 矩阵的最大值和最小值
*/
static int max[][][];
static int min[][][];
static int n;
static int m;
static int limit;
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
n = scanner.nextInt();
m = scanner.nextInt();
max = new int[m+1][n+1][n+1];//max[k][i][j] 第k列 从第i行到第j行的最大值
min = new int[m+1][n+1][n+1];//min[k][i][j] 第k列 从第i行到第j行的最小值
int a[][] = new int[n+1][m+1];
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
a[i][j] = scanner.nextInt();
// max[j][i][i] = a[i][j];
// min[j][i][i] = a[i][j];
}
}
limit = scanner.nextInt();
//预处理 max和min max[k][i][j] = max(max[k][i][j-1],max[k][j][j])
//如何遍历也是关键 k在最外层循环,依次是i、j
//也要先进行初始化
//max[k][i][j] i=j
for(int k = 1;k <= m;k++) {
for (int i = 1; i <= n; i++) {
max[k][i][i] = a[i][k];
min[k][i][i] = a[i][k];
}
}
for(int k = 1;k <= m;k++) {
for (int i = 1; i <= n; i++) {
for (int j = i + 1; j <= n; j++) {
max[k][i][j] = Math.max(max[k][i][j-1], max[k][j][j]);
min[k][i][j] = Math.min(min[k][i][j-1], min[k][j][j]);
}
}
}
long area = 0;
//for循环确定矩形的上下底
//二分求的是满足条件的最大值
for (int i = 1; i < n+1; i++) {
for (int j = i; j < n+1; j++) {
int l = 1,r = m;
while(l < r) {
// System.out.println(l);
int mid = (l + r + 1) / 2;
if(check(mid,i,j)) {
l = mid;
}else {
r = mid - 1;
}
}
if(check(l, i, j)) {
area = Math.max(area, l*(j - i + 1));
}
}
}
System.out.println(area);
}
private static boolean check(int k,int x1,int x2) {//k表示窗口的大小,矩形的长度;x1、x2分别表示矩形的上下底
// TODO Auto-generated method stub
//用了两个滑动窗口 一个找窗口内的最大值 一个找窗口内的最小值
//使用单调队列实现滑动窗口
Deque<Integer> qmax = new ArrayDeque<Integer>();
Deque<Integer> qmin = new ArrayDeque<Integer>();
for (int i = 1; i <= m; i++) {//i表示窗口的终点,k表示窗口的长度,但是这里i不能从k开始遍历,只能从1开始遍历
//矩阵的最小值
//单调队列 应该是递增的 队尾元素大于当前元素 小于当前元素
//求子矩阵的最大值-最小值不大于给定的limit 同时 这个矩阵面积尽可能大
//检查此时队列中前面小的元素是否已经在窗口外了
//qmin记录的是矩阵的列的大小
while(!qmin.isEmpty() && qmin.peekFirst() < (i - k + 1)) qmin.pollFirst();
//目前求的是最小值,若队列中队尾元素大于当前元素,则她不管用了,把它删除,最后放入当前元素。队头元素是最小值
while(!qmin.isEmpty() && min[qmin.peekLast()][x1][x2] > min[i][x1][x2]) qmin.pollLast();
qmin.addLast(i);
//矩阵的最大值
//单调队列 单调递减的 队尾元素小于当前元素
//检查此时队列中前面大的元素是否已经在窗口外了
while(!qmax.isEmpty() && qmax.peekFirst() < (i - k + 1)) qmax.pollFirst();
//目前求的是最da值,若队列中队尾元素小于当前元素,则她不管用了,把它删除,最后放入当前元素。队头元素是最大值
while(!qmax.isEmpty() && max[qmax.peekLast()][x1][x2] < max[i][x1][x2]) qmax.pollLast();
qmax.addLast(i);
if(i >= k && max[qmax.peekFirst()][x1][x2] - min[qmin.peekFirst()][x1][x2] <= limit) {
return true;
}
}
return false;
}
}