题目:
本题为填空题,只需要算出结果后,在代码中使用输出语句将所填结果输出即可。
问题描述:
小蓝有一个 100 行 100 列的矩阵,矩阵的左上角为 1 。其它每个位置正好比其左边的数大 2,比其上边的数大 1 。
例如,第 1 行第 2 列为 3,第 2 行第 2 列 为 4,第 10 行第 20列为 48。
小蓝想在矩阵中找到一个由连续的若干行、连续的若干列组成的子矩阵,使得其和为
2022,请问这个子矩阵中至少包含多少个元素(即子矩阵的行数和列数的乘积)。
答案:12
dfs + 回溯:
解题:
做这道题我是用的 dfs + 回溯的做法,思路就是从任意一点作为起点,遍历出一个矩形等于2022,就记录面积。思路与 最大子矩阵(暴力破解)思路完全一致。想要练 dfs + 回溯的话,可以用这道题试试手。
本次主要是分析下面的枚举思路的,所以直接给代码了。
dfs + 回溯代码:
import java.util.*;
public class text{
public static int a[][];
public static void main(String[] args){
a = new int[100][100];
for(int i = 0; i < 100; i ++) {
for(int j = 0; j < 100; j ++) {
if(i == 0 && j == 0) {
a[i][j] = 1;
continue;
}
if(j == 0) {
a[i][j] = a[i - 1][j] + 1;
continue;
}
a[i][j] = a[i][j - 1] + 2;
}
}
for(int i = 50; i < 100; i ++)
for(int j = 50; j < 100; j ++) {
minone = Integer.MAX_VALUE;
int bf = a[i][j];
dfs(i, j, a[i][j]);
a[i][j] = bf;
lastmin = Math.min(lastmin, minone);
mini = Integer.MAX_VALUE;
minj = Integer.MAX_VALUE;
maxi = Integer.MIN_VALUE;
maxj = Integer.MIN_VALUE;
}
System.out.println(lastmin);
}
public static int minone = Integer.MAX_VALUE;
public static int lastmin = Integer.MAX_VALUE;
public static int mini = Integer.MAX_VALUE;
public static int minj = Integer.MAX_VALUE;
public static int maxi = Integer.MIN_VALUE;
public static int maxj = Integer.MIN_VALUE;
public static void updata(int x, int y) {
mini = Math.min(x, mini);
minj = Math.min(y, minj);
maxi = Math.max(x, maxi);
maxj = Math.max(y, maxj);
}
public static void dfs(int i, int j, int sum) {
a[i][j] = 200;
updata(i, j);
if(sum == 2022 && check()) {
minone = Math.min(minone, (maxj - minj + 1) * (maxi - mini + 1));
return;
}
if(sum > 2022) return;
int fx[] = {-1, 1, 0, 0};
int fy[] = {0, 0, -1, 1};
int fxx = 0, fyy = 0;
for(int k = 0; k < 4; k ++) {
fxx = i + fx[k];
fyy = j + fy[k];
int bmi = mini;
int bmj = minj;
int bxi = maxi;
int bxj = maxj;
if(check()) {
if(fxx >= 50 && fxx <= 99 && fyy >= 50 && fyy <= 99 && a[fxx][fyy] != 200) {
int bf = a[fxx][fyy];
dfs(fxx, fyy, sum + bf);
a[fxx][fyy] = bf;
}
}
else {
if(fxx >= mini && fxx <= maxi && fyy >= minj && fyy <= maxj && a[fxx][fyy] != 200) {
int bfr = a[fxx][fyy];
dfs(fxx, fyy , sum + bfr);
a[fxx][fyy] = bfr;
}
}
mini = bmi;
minj = bmj;
maxi = bxi;
maxj = bxj;
}
}
public static boolean check() {
for(int i = mini; i <= maxi; i ++)
for(int j = minj; j <= maxj ; j ++)
if(a[i][j] != 200) return false;
return true;
}
}
需要注意的是上述代码,从50~100开始遍历就可以了,前面(1 ~ 50)每个点的值太小了,要20多个点甚至50多个点菜才可能构成2022,一个是太费时间,另一个就是人家题目要求的是最小值。所以答案应该在数大的后半部分。
枚举法:
解题:
这个枚举法是人家给的答案,对答案的解析也比较粗糙,但是代码量少,且时间复杂度还可以接受1000
个点以内用这个方法还是很不错的。特意学了一下,以后混分用😄。
原解析:
由于上述,解析说的太粗糙了,我认真学习了一下代码,以下是我对这个枚举的理解:
1.首先需要知道,此枚举法的下标是从(1, 1)开始的,因为其核心公式需要借助数组(0,0)点为0这个初始值
2.需要构造题目描述的矩阵,此枚举法,采用先构造第一行
再构造所有列
的方式
代码:
a[1][1] = 1;
for(int i = 2 ; i <= 100 ; i ++) a[1][i] = a[1][i - 1] + 2;
for(int i = 2;i <= 100;i++)
for(int j = 1;j <= 100;j++)
a[i][j] = a[i - 1][j] + 1;
3.核心1:即单独构造一个sum二维数组且sum[i][j]表示纵坐标满足x∈[0 ~ i],横坐标满足y∈[0 ~ j]内所有a[x][y]和。
例如:
更新sum数组的代码:
for(int i = 1;i <= 100;i++){
for(int j = 1;j <= 100;j++)
sum[i][j] = sum[i][j - 1] + a[i][j];
for(int j = 1;j <= 100;j++)
sum[i][j] = sum[i][j] + sum[i - 1][j];
}
即先更新本行值,再将上行的值加过来。
4.枚举两个点(需要四重for循环)一这两个点作为查询矩阵的左上角,和右下角两个点分别以 (i , j) , (k, l)表示如果符合值为2022就计算矩阵面积N = (k - i + 1) * (l - j + 1)
并更新元素值。(这个不难证明画个图就知道了)
代码:
ans = Math.min(ans,(k - i + 1) * (l - j + 1));
5.核心2:还记得我们枚举的矩阵左上角为(i, j) ,右下角为(k, l)么,那么怎么结合sum数组计算出这个矩阵内所有元素值的总和呢?
以下是图解:
1. 注意上述我们用的a数组进行举例的
2. sum[k][l]就是整个a数组的元素值的总和,也就是整个矩阵
3. 我们目标是求出红色阴影部分所有元素值的总和,就是我们的代求子矩阵,所以我们必需用整个矩阵减去上图蓝色部分,和上图橙色部分。 但是蓝色和橙色部分被减过两次。所所以必须要再加上一个sum[i - 1][j - 1],至于为什么i,j要减1,因为AC,AD边都属于我们子矩阵部分,不能被减去。
代码:
public static int calc(int i , int j , int k , int l){
return sum[k][l] - sum[k][j - 1] - sum[i - 1][l] + sum[i - 1][j - 1];
}
本题枚举完整代码如下:
import java.util.*;
public class Main {
static int [][] a = new int [150][150];//存储矩阵
static int [][] sum = new int [150][150];
public static int calc(int i , int j , int k , int l){
return sum[k][l] - sum[k][j - 1] - sum[i - 1][l] + sum[i - 1][j - 1];
}
public static void main(String[] args) {
a[1][1] = 1;
for(int i = 2 ; i <= 100 ; i ++) a[1][i] = a[1][i - 1] + 2;
for(int i = 2;i <= 100;i++)
for(int j = 1;j <= 100;j++)
a[i][j] = a[i - 1][j] + 1;
for(int i = 1;i <= 100;i++){
for(int j = 1;j <= 100;j++)
sum[i][j] = sum[i][j - 1] + a[i][j];
for(int j = 1;j <= 100;j++)
sum[i][j] = sum[i][j] + sum[i - 1][j];
}
int ans = 100 * 100;
for(int i = 1;i <= 100;i++)
for(int j = 1;j <= 100;j++)
for(int k = i;k <= 100;k++)
for(int l = j;l <= 100;l++)
if(calc(i, j, k, l) == 2022)
ans = Math.min(ans,(k - i + 1) * (l - j + 1));
System.out.println(ans);
}
}
总结:
总结来所本题官方代码,思路很新颖,在特定矩阵题目的话还是很实用的。就是点数如果超过1000就就会T到爆炸,点数在1000以内还是很实用的。
至于 dfs + 回溯,依旧很强。就是特定情况下,需要的代码比较多。
混分不易😭。