第16届蓝桥杯模拟赛题解 第三场 Java
填空题
1【问题描述】
如果一个数 p 是个质数,同时又是整数 a 的约数,则 p 称为 a 的一个质因数。
请问, 2024 的最大的质因数是多少?
【答案提交】
这是一道结果填空的题,你只需要算出结果后提交即可。本题的结果为一个整数,在提交答案时只填写这个整数,填写多余的内容将无法得分。
- 答案 23
~~~java
public class Pro1 {
public static void main(String[] args) {
int n = 2024;
for (int i = (int) Math.sqrt(n); i >= 2; i--) { // 从大到小枚举
if (n % i == 0 && get(i)) { // 判断是否是因数且是质数
System.out.println(i); // 输出结果
return;
}
}
System.out.println(2024); // 如果没有找到,输出2024本身(2024是质数的情况)
}
// 判断一个数是否是质数
static boolean get(int x) {
if (x < 2) return false;
for (int i = 2; i <= x / i; i++) {
if (x % i == 0) return false;
}
return true;
}
}
~~~
2【问题描述】
对于两个整数 a, b,既是 a 的整数倍又是 b 的整数倍的数称为 a 和 b 的公倍数。公倍数中最小的正整数称为 a 和 b 的最小公倍数。
请问, 2024 和 1024 的最小公倍数是多少?
【答案提交】
这是一道结果填空的题,你只需要算出结果后提交即可。本题的结果为一个整数,在提交答案时只填写这个整数,填写多余的内容将无法得分。
- 答案
~~~java
public class Pro2 {
public static void main(String[] args) {
int a = 2024;
int b = 1024;
int lcm = a * b / gcd(a, b); // 利用公式计算最小公倍数
System.out.println(lcm); // 输出结果
}
// 计算最大公约数(GCD)的函数
static int gcd(int a, int b) {
while (b != 0) {
int temp = b;
b = a % b; // 欧几里得算法
a = temp;
}
return a; // 返回最大公约数
}
}
~~~
3【问题描述】
如果一个数 p 是个质数,同时又是整数 a 的约数,则 p 称为 a 的一个质因数。
请问, 2024 的所有质因数的和是多少?
【答案提交】
这是一道结果填空的题,你只需要算出结果后提交即可。本题的结果为一个整数,在提交答案时只填写这个整数,填写多余的内容将无法得分。
- 答案 29
~~~java
public class Pro3 {
public static void main(String[] args) {
int n = 2024;
int sum = 0; // 用于存储所有质因数的和
// 从最小的质数2开始分解
for (int i = 2; i <= n; i++) {
// 判断i是否是n的因数
while (n % i == 0) {
sum += i; // 将i累加到sum中
n /= i; // 继续分解n
}
}
System.out.println(sum); // 输出所有质因数的和
}
}
~~~
4【问题描述】
请问,在不超过 2024 的数中,最大的质数是多少?
【答案提交】
这是一道结果填空的题,你只需要算出结果后提交即可。本题的结果为一个整数,在提交答案时只填写这个整数,填写多余的内容将无法得分。
- 答案 2017
~~~java
public class Pro4{
public static void main(String[] args) {
int n = 2024;
while (n > 1) {
if (isPrime(n)) {
System.out.println(n); // 输出最大质数
return;
}
n--;
}
}
// 判断一个数是否是质数
static boolean isPrime(int x) {
if (x < 2) return false; // 小于2的数不是质数
for (int i = 2; i <= x / i; i++) {
if (x % i == 0) return false; // 如果有因数,则不是质数
}
return true; // 如果没有因数,则是质数
}
}
~~~
5【问题描述】
如果两个整数 a, b 除了 1 以外,没有其它的公约数,则称整数 a 与 b 互质。
请问,与 2024 互质的数中(包括1),第 2024 小的数是多少?
【答案提交】
这是一道结果填空的题,你只需要算出结果后提交即可。本题的结果为一个整数,在提交答案时只填写这个整数,填写多余的内容将无法得分。
- 答案 2023
~~~java
public class Pro1 {
public static void main(String[] args) {
int n = 2024;
int count = 0;
int i = 1;
while (count < 2024) {
if (gcd(i, n) == 1) {
count++;
}
i++;
}
System.out.println(i - 1); // 输出第2024小的与2024互质的数
}
// 计算最大公约数(GCD)的函数
static int gcd(int a, int b) {
while (b != 0) {
int temp = b;
b = a % b;
a = temp;
}
return a;
}
}
~~~
6 【问题描述】
对于字符串 S=ANQNANBNQNANQNQNBNINQNQNANQNINANQNANBNQNANQNQNBNBNQNQNANQNINANQNANBNQNANQNQNBNINQNQNANQNINBNQNANBNQN ,请找到 S 的一个长度不超过 10 的子串 A,使得(A的长度)乘以(A在S中出现的次数)最大。
请问这个子串是什么?(如果有多个满足条件的,请回答字典序最小的)。
【答案提交】
这是一道结果填空的题,你只需要算出结果后提交即可。本题的结果为一个字符串,在提交答案时只填写这个字符串,填写多余的内容将无法得分。
- 答案
~~~java
import java.util.HashMap;
import java.util.Map;
public class Pro1 {
public static void main(String[] args) {
String S = "ANQNANBNQNANQNQNBNINQNQNANQNINANQNANBNQNANQNQNBNINQNQNANQNINBNQNANBNQN";
int maxScore = 0; // 用于记录最大分数
String bestSubstring = ""; // 用于记录最优子串
// 枚举所有可能的子串
for (int len = 1; len <= 10; len++) { // 子串长度不超过10
for (int i = 0; i <= S.length() - len; i++) {
String substring = S.substring(i, i + len); // 获取子串
int count = countOccurrences(S, substring); // 计算子串在S中的出现次数
int score = len * count; // 计算分数
// 更新最优子串
if (score > maxScore || (score == maxScore && substring.compareTo(bestSubstring) < 0)) {
maxScore = score;
bestSubstring = substring;
}
}
}
System.out.println(bestSubstring); // 输出结果
}
// 计算子串在字符串S中的出现次数
static int countOccurrences(String S, String substring) {
int count = 0;
int index = 0;
while ((index = S.indexOf(substring, index)) != -1) {
count++;
index += substring.length(); // 移动到子串的下一个位置
}
return count;
}
}
~~~
7【问题描述】
如果一个字符串中只包含字符 0 和字符 1,则称为一个 01 串(包含全为 0 的串和全为 1 的串)。
请问有多少个长度为 24 的 01 串,满足任意 5 个连续的位置中不超过 3 个位置的值为 1 。
【答案提交】
这是一道结果填空的题,你只需要算出结果后提交即可。本题的结果为一个整数,在提交答案时只填写这个整数,填写多余的内容将无法得分。
- 答案 167960
~~~java
public class Pro7 {
public static void main(String[] args) {
int n = 24;
int[][] dp = new int[n + 1][4]; // dp[i][j]表示长度为i的01串,且最后j个位置中1的个数不超过3的01串的数量
// 初始化
dp[1][0] = 1; // 长度为1的01串,最后0个位置中1的个数不超过3,即"0"
dp[1][1] = 1; // 长度为1的01串,最后1个位置中1的个数不超过3,即"1"
// 状态转移
for (int i = 2; i <= n; i++) {
for (int j = 0; j < 4; j++) {
dp[i][j] += dp[i - 1][j]; // 最后一位是0
if (j > 0) {
dp[i][j] += dp[i - 1][j - 1]; // 最后一位是1
}
}
}
// 计算结果
int result = 0;
for (int j = 0; j < 4; j++) {
result += dp[n][j];
}
System.out.println(result); // 输出结果
}
}
~~~
大题
1【问题描述】
小蓝种了一块玉米地,玉米地长 n 米,宽 m 米,每平方米产玉米 a 千克。请问小蓝的玉米地一共能产多少千克玉米?
【输入格式】
输入三行。第一行包含一个正整数 n ,第二行包含一个正整数 m ,第三行包含一个正整数 a 。
【输出格式】
输出一行,包含一个整数,表示答案。
【样例输入】
20
24
900
【样例输出】
432000
【评测用例规模与约定】
对于所有评测用例,1 <= n <= 1000, 1 <= m <= 1000, 1 <= a <= 2000。
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt(); // 读取玉米地的长
int m = sc.nextInt(); // 读取玉米地的宽
int a = sc.nextInt(); // 读取每平方米的产量
long total = (long) n * m * a; // 计算总产量
System.out.println(total); // 输出结果
}
}
2【问题描述】
小蓝有一个数组 a[1], a[2], ..., a[n], 一个“再创新高”的位置是指一个位置 p ,a[p] 的值比之前每个位置的值都大。
请求出小蓝的数组中有多少个再创新高的位置。
【输入格式】
输入的第一行包含一个整数 n 。
第二行包含 n 个整数,相邻数之间使用一个空格分隔,依次表示 a[1], a[2], ..., a[n] 。
【输出格式】
输出一行,包含一个整数,表示答案。
【样例输入】
8
1 2 3 4 5 6 6 6
【样例输出】
6
【样例输入】
9
3 2 1 6 5 4 9 8 7
【样例输出】
3
【评测用例规模与约定】
对于 30% 的评测用例,1 <= n <= 100,0 <= a[i] <= 1000。
对于 60% 的评测用例,1 <= n <= 1000,0 <= a[i] <= 1000。
对于所有评测用例,1 <= n <= 10000,0 <= a[i] <= 1000000。
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt(); // 读取数组长度
int[] a = new int[n];
for (int i = 0; i < n; i++) {
a[i] = sc.nextInt(); // 读取数组元素
}
int count = 0; // 用于记录“再创新高”的位置个数
int max = a[0]; // 初始化最大值为第一个元素
for (int i = 1; i < n; i++) {
if (a[i] > max) {
max = a[i]; // 更新最大值
count++; // 计数加1
}
}
System.out.println(count + 1); // 输出结果,加上第一个元素
}
}
3【问题描述】
给定四个字符串 a, b, c, d,请将这四个字符串按照任意顺序依次连接拼成一个字符串。
请问拼成的字符串字典序最小是多少?
【输入格式】
输入四行,每行包含一个字符串。
【输出格式】
输出一行包含一个字符串,表示答案。
【样例输入】
LAN
LAN
QIAO
BEI
【样例输出】
BEILANLANQIAO
【评测用例规模与约定】
对于所有评测用例,输入的字符串非空串,由大写字母组成,长度不超过 1000 。
import java.util.Arrays;
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
String[] strings = new String[4];
for (int i = 0; i < 4; i++) {
strings[i] = sc.nextLine(); // 读取四个字符串
}
Arrays.sort(strings); // 将字符串数组按照字典序排序
StringBuilder result = new StringBuilder();
for (String str : strings) {
result.append(str); // 将排序后的字符串依次拼接
}
System.out.println(result.toString()); // 输出结果
}
}
4【问题描述】
蓝桥村正在给村民们发放礼物。礼物通过一个礼物发放机完成。
村民们在机器前排着队领取礼物。
每个礼物有一个价值 v[i] ,有的高,有的低。每位村民有自己对于礼物的期望值 e[i] 。
礼物发放机每次会显示一个礼物,如果礼物的价值大于等于村民的期望值,村民就会高兴地把礼物拿走,并离开礼物发放机。如果礼物的价值比村民的期望值低,村民会让这个礼物取消,并让礼物发放机显示下一个礼物,并重新检查是否满足期望。村民会重复让礼物发放机显示下⼀个礼物,直到礼物发放机没有更多可以显示的礼物或礼物的价值大于等于自己的期望值。
如果礼物发放机中的所有礼物都显示完了,那么还没领到礼物的村民就无法领取礼物了。
如果所有的村民都领到了礼物,而礼物发放机还有礼物显示,村民们也不会再领取礼物。
现在,小蓝知道了每位村民的期望值,也知道了礼物发放机上礼物的显示顺序,请问总共有多少村民拿到了礼物?
【输入格式】
输入的第一行包含一个整数 n ,表示村民的个数。
第二行包含 n 个整数,相邻数之间使用一个空格分隔,依次表示排队的每位村民的期望值 e[i] 。
第三行包含一个整数 m ,表示礼物发放机会显示的礼物个数。
第四行包含 m 个整数,相邻数之间使用一个空格分隔,依次表示礼物发放机显示的礼物的价值 v[i] 。
【输出格式】
输出一行,包含一个整数,表示答案。
【样例输入】
6
6 5 5 3 6 0
9
9 9 8 2 4 4 3 5 3
【样例输出】
4
【样例说明】
前 4 位村民依次取到了第 1, 2, 3, 5 件礼物。后面的礼物未能满足第 5 位村民。
【评测用例规模与约定】
对于 30% 的评测用例,1 <= n, m <= 20 , 0 <= e[i], v[i] <= 100 。
对于 60% 的评测用例,1 <= n, m <= 300 , 0 <= e[i], v[i] <= 10000 。
对于所有评测用例,1 <= n, m <= 10000 , 0 <= e[i], v[i] <= 1000000 。
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
// 读取村民数量
int n = scanner.nextInt();
int[] e = new int[n]; // 村民的期望值数组
for (int i = 0; i < n; i++) {
e[i] = scanner.nextInt();
}
// 读取礼物数量
int m = scanner.nextInt();
int[] v = new int[m]; // 礼物的价值数组
for (int i = 0; i < m; i++) {
v[i] = scanner.nextInt();
}
// 计算拿到礼物的村民数量
int villagerIndex = 0; // 当前村民的索引
int giftIndex = 0; // 当前礼物的索引
int receivedGifts = 0; // 拿到礼物的村民数量
while (villagerIndex < n && giftIndex < m) {
if (v[giftIndex] >= e[villagerIndex]) {
// 当前礼物满足当前村民的期望值
receivedGifts++;
villagerIndex++; // 移动到下一个村民
}
giftIndex++; // 移动到下一个礼物
}
// 输出结果
System.out.println(receivedGifts);
scanner.close();
}
}
5【问题描述】
小蓝有一个 n 行 m 列的矩阵 a[i][j] ,他想着矩阵中找出一个“十”字形状的区域,使得区域内的值的和最大。
一个“十”字形状的区域可以由两个行号 r1 、 r2 和两个列号 c1 、 c2 表示。“十”字的区域内包括第 r1 行到 r2 行的所有元素,以及第 c1 列到 c2 列的所有元素,既不在这几行也不在这几列的元素不在区域内。
为了保证是一个“十”字的形状,必须满足 1 < r1 <= r2 < n,1 < c1 <= c2 < m。
【输入格式】
输入的第一行包含两个整数 n, m ,分别表示行数和列数。
接下来 n 行,每行包含 m 个整数,相邻数之间使用一个空格分隔,依次表示矩阵的每行每列的值,本部分的第 i 行第 j 列表示 a[i][j] 。
【输出格式】
输出一行包含一个整数,表示最大的和。
【样例输入】
5 6
1 -1 2 -2 3 -3
-1 2 -2 3 -3 4
2 -2 3 -3 4 -4
-2 3 -3 4 -4 5
3 -3 4 -4 5 -5
【样例输出】
14
【样例说明】
有两种方法可以得到最大的和。第一种是取 r1=2, r2=4, c1=3, c2=5,第二种是取 r1=2, r2=4, c1=5, c2=5 。
【评测用例规模与约定】
对于 30% 的评测用例,3 <= n, m <= 30 ,-1000 <= a[i][j] <= 1000 。
对于 60% 的评测用例,3 <= n, m <= 100 ,-1000 <= a[i][j] <= 1000 。
对于所有评测用例,3 <= n <= 100, 3 <= m <= 5000 ,-1000 <= a[i][j] <= 1000 。
问题分析与解题思路
1. 问题背景
我们需要在一个矩阵中找到一个“十”字形状区域,使得该区域内的值的和最大。这个“十”字形状由两部分组成:
- 行段:从第 (r1) 行到第 (r2) 行的所有元素。
- 列段:从第 (c1) 列到第 (c2) 列的所有元素。
同时,题目要求:
- (1 < r1 \leq r2 < n),即行段不能包含第一行或最后一行。
- (1 < c1 \leq c2 < m),即列段不能包含第一列或最后一列。
2. 核心思路
为了高效计算“十”字形状区域的和,我们需要快速计算任意行段和列段的和。为此,我们使用前缀和技术:
- 行前缀和:快速计算任意行段的和。
- 列前缀和:快速计算任意列段的和。
通过前缀和,我们可以在 (O(1)) 时间内计算任意行段或列段的和,从而避免暴力计算的高时间复杂度。
3. 枚举所有可能的“十”字形状
我们需要枚举所有可能的 (r1, r2, c1, c2) 组合,计算每个“十”字形状区域的和,并记录最大和。具体步骤如下:
- 枚举所有可能的行范围 ([r1, r2])。
- 枚举所有可能的列范围 ([c1, c2])。
- 对于每个“十”字形状区域,计算其行段和列段的和。
- 减去重复计算的交叉点的值。
- 更新最大和。
4. 时间复杂度分析
- 前缀和计算:行前缀和和列前缀和的计算时间复杂度为 (O(n \times m))。
- 枚举“十”字形状:需要四重循环枚举 (r1, r2, c1, c2),时间复杂度为 (O(n2 \times m2))。
虽然时间复杂度较高,但由于题目限制 (n \leq 100) 和 (m \leq 5000),这种方法在合理范围内。
详细代码实现与注释
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
// 读取矩阵的行数和列数
int n = scanner.nextInt(); // 行数
int m = scanner.nextInt(); // 列数
// 读取矩阵
int[][] a = new int[n][m];
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
a[i][j] = scanner.nextInt();
}
}
// 计算行的前缀和
int[][] rowPrefixSum = new int[n][m];
for (int i = 0; i < n; i++) {
rowPrefixSum[i][0] = a[i][0]; // 初始化第一列
for (int j = 1; j < m; j++) {
rowPrefixSum[i][j] = rowPrefixSum[i][j - 1] + a[i][j]; // 计算行前缀和
}
}
// 计算列的前缀和
int[][] colPrefixSum = new int[n][m];
for (int j = 0; j < m; j++) {
colPrefixSum[0][j] = a[0][j]; // 初始化第一行
for (int i = 1; i < n; i++) {
colPrefixSum[i][j] = colPrefixSum[i - 1][j] + a[i][j]; // 计算列前缀和
}
}
// 枚举所有可能的“十”字形状区域
int maxSum = Integer.MIN_VALUE; // 用于记录最大和
for (int r1 = 1; r1 < n - 1; r1++) { // 枚举行范围的起始行
for (int r2 = r1; r2 < n - 1; r2++) { // 枚举行范围的结束行
for (int c1 = 1; c1 < m - 1; c1++) { // 枚举列范围的起始列
for (int c2 = c1; c2 < m - 1; c2++) { // 枚举列范围的结束列
// 计算行段的和
int rowSum = 0;
for (int i = r1; i <= r2; i++) { // 遍历行范围
rowSum += rowPrefixSum[i][c2] - (c1 > 0 ? rowPrefixSum[i][c1 - 1] : 0);
}
// 计算列段的和
int colSum = 0;
for (int j = c1; j <= c2; j++) { // 遍历列范围
colSum += colPrefixSum[r2][j] - (r1 > 0 ? colPrefixSum[r1 - 1][j] : 0);
}
// 避免重复计算交叉点的值
int crossSum = a[r1][c1] + a[r1][c2] + a[r2][c1] + a[r2][c2];
// 计算“十”字形状区域的和
int sum = rowSum + colSum - crossSum;
// 更新最大和
maxSum = Math.max(maxSum, sum);
}
}
}
}
// 输出最大和
System.out.println(maxSum);
scanner.close();
}
}
详细注释说明
- 读取输入数据:
-
- 读取矩阵的行数 (n) 和列数 (m)。
- 读取矩阵 (a[i][j]) 的值。
- 计算行前缀和:
-
rowPrefixSum[i][j]
表示第 (i) 行从第 0 列到第 (j) 列的和。- 通过
rowPrefixSum[i][j] = rowPrefixSum[i][j - 1] + a[i][j]
快速计算。
- 计算列前缀和:
-
colPrefixSum[i][j]
表示第 (j) 列从第 0 行到第 (i) 行的和。- 通过
colPrefixSum[i][j] = colPrefixSum[i - 1][j] + a[i][j]
快速计算。
- 枚举“十”字形状区域:
-
- 使用四重循环枚举所有可能的 (r1, r2, c1, c2)。
- 对于每个“十”字形状区域,分别计算行段和列段的和。
- 通过行前缀和和列前缀和快速计算和,避免暴力计算。
- 避免重复计算交叉点的值:
-
- 交叉点的值在行段和列段中都被计算了两次,需要减去重复计算的部分。
- 更新最大和:
-
- 每次计算一个“十”字形状区域的和后,更新最大和。
示例运行
对于输入:
5 6
1 -1 2 -2 3 -3
-1 2 -2 3 -3 4
2 -2 3 -3 4 -4
-2 3 -3 4 -4 5
3 -3 4 -4 5 -5
程序输出:
14
总结
通过前缀和技术,我们可以在 (O(1)) 时间内快速计算任意行段和列段的和,从而高效地枚举所有可能的“十”字形状区域。这种方法在合理的时间复杂度内解决了问题。