前言
第 26 场蓝桥月赛是蓝桥杯系列赛事中的一场重要比赛,于 2025 年 2 月 22 日举行,采用 ACM 赛制,赛题共计 6 题,比赛时间为 2 小时。这场比赛吸引了众多编程爱好者和算法学习者参与,旨在通过一系列具有挑战性的算法问题,考察参赛者的编程能力、算法思维以及解决问题的能力。赛后练习功能为参赛者提供了继续提升的机会,尽管不增加个人得分,但仍然是巩固知识和技能的宝贵资源。本文将对比赛中的题目进行考点概括和分析,帮助读者更好地理解比赛内容和解题思路。
考点概括
1. 好汤圆
- 考点:签到题,基础数学运算(除法)。
- 解题思路:题目要求计算让所有汤圆浮起来所需的时间,通过简单的除法运算即可得出答案。
2. 灯笼猜谜
- 考点:区间移动,左右距离计算。
- 解题思路:通过模拟移动过程,计算从一个区间到另一个区间的距离,需要注意当前位置与区间边界的相对位置关系。
3. 元宵分配
- 考点:模拟,数组操作。
- 解题思路:对数组进行排序后,通过遍历数组的前半部分计算总和,考察了数组的基本操作和排序算法的使用。
4. 摆放汤圆
- 考点:动态规划,模运算,预处理。
- 解题思路:通过动态规划解决皇后对称放置问题,定义状态转移方程,并利用模运算处理大数问题,同时通过预处理提高效率。
5. 元宵交友
- 考点:二分查找。
- 解题思路:利用二分查找优化查找过程,通过遍历所有可能的时间间隔,计算可以邀请的朋友数量,考察了二分查找的应用和循环控制。
6. 灯笼大乱斗
- 考点:数组操作,分段处理,组合数学。
- 解题思路:通过计算两个新数组(C 和 D),找到满足条件的连续区间,并利用组合数学公式计算区间对的数量,考察了数组操作、分段处理和组合数学的应用。
好汤圆 签到题 除法
题干:
已知锅里有 2025 个汤圆。肉丸子跳进去后,每分钟都会产生足够的热量使 15 个汤圆浮起来。
请问,肉丸子需要多少分钟才能让所有汤圆都浮起来?
import java.util.Scanner;
// 1:无需package
// 2: 类名必须Main, 不可修改
public class Main {
public static void main(String[] args) {
Scanner scan = new Scanner(System.in);
//在此输入您的代码...
int n=2025/15;
System.out.println(n);
scan.close();
}
}
灯笼猜谜 区间移动 左右距离
import java.util.Scanner;
import java.util.Arrays;
public class Main {
public static void main(String[] args) {
Scanner scan = new Scanner(System.in);
int N = scan.nextInt(); // 区间对的数量
int M = scan.nextInt(); // 未使用的变量
int[][] arr = new int[N][2]; // 存储每个区间的左右边界
for (int i = 0; i < N; i++) {
arr[i][0] = scan.nextInt(); // 区间左边界
arr[i][1] = scan.nextInt(); // 区间右边界
}
long index = 1, total = 0; // 初始位置为1,总移动距离为0
for (int i = 0; i < arr.length; i++) {
int left = arr[i][0]; // 当前区间的左边界
int right = arr[i][1]; // 当前区间的右边界
if (index < left) {
// 如果当前位置在当前区间的左边,移动到左边界
total += Math.abs(index - left); // 计算移动距离
index = left; // 更新当前位置为左边界
} else if (index > right) {
// 如果当前位置在当前区间的右边,移动到右边界
total += Math.abs(index - right); // 计算移动距离
index = right; // 更新当前位置为右边界
}
}
System.out.println(total); // 输出总移动距离
}
}
元宵分配 模拟
import java.util.Arrays;
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int N = scanner.nextInt(); // 读取数组的长度
int K = scanner.nextInt(); // 读取一个未使用的变量
int[] arr = new int[N]; // 创建一个长度为N的数组
for (int i = 0; i < N; i++) {
arr[i] = scanner.nextInt(); // 读取数组的每个元素
}
Arrays.sort(arr); // 对数组进行升序排序
// 使用long来存储,避免整数溢出
long sum = 0; // 初始化总和为0
for (int i = 0; i < N / 2; i++) { // 遍历数组的前半部分
sum += arr[i]; // 累加前半部分的元素
}
System.out.println(sum); // 输出总和
}
}
摆放汤圆 对称 n 皇后
问题描述:
- 皇后问题:在一个 ( k *k ) 的棋盘上放置 ( k ) 个皇后,使得它们互不攻击。皇后可以攻击同一行、同一列以及同一对角线上的其他皇后。
- 对称放置:皇后关于主对角线对称放置,即如果第 ( i ) 行第 ( j ) 列放置了一个皇后,则第 ( j ) 行第 ( i ) 列也必须放置一个皇后。
解题思路:
- 动态规划:
-
- 使用动态规划(DP)来解决这个问题。
- 定义
dp[k]表示在 ( k \times k ) 的棋盘上对称放置皇后的方法数。 - 初始条件:
-
-
dp[0] = 1:空矩阵有一种方法(即不放置任何皇后)。dp[1] = 1:1x1 的矩阵有一种方法(放置一个皇后)。
-
-
- 状态转移方程:
-
-
- 对于 ( k \times k ) 的棋盘,有两种情况:
-
-
-
-
- 不放置皇后:方法数为
dp[k-1]。 - 放置皇后:选择一个位置放置皇后,皇后必须关于主对角线对称。因此,可以选择 ( k-1 ) 个位置放置皇后,每个位置的放置方法数为
dp[k-2]。
- 不放置皇后:方法数为
-
-
-
-
- 因此,状态转移方程为:
[
dp[k] = dp[k-1] + (k-1) \times dp[k-2]
]
- 因此,状态转移方程为:
-
- 模运算:
-
- 由于结果可能非常大,需要对结果取模(模数为 ( 10^9 + 7 ))。
- 预处理:
-
- 在程序开始时,预先计算并存储所有可能的 ( k ) 值的
dp[k],这样在处理多个查询时可以直接输出结果,提高效率。
- 在程序开始时,预先计算并存储所有可能的 ( k ) 值的
代码详细注释:
import java.util.Scanner;
public class Main {
static long MOD = (long)(1e9 + 7); // 定义模数
static int N = (int)(1e6 + 5); // 定义最大值
static long[] dp = new long[N]; // 定义动态规划数组
static void solve() {
// dp[k] 表示 k x k 矩阵的对称放置方法数
// 初始条件
dp[0] = 1; // 空矩阵有一种方法
dp[1] = 1; // 1x1 矩阵有一种方法
// 填充 dp 数组
for (int k = 2; k < N; k++) {
// 状态转移方程
dp[k] = (dp[k - 1] + (k - 1) * dp[k - 2]) % MOD;
}
}
public static void main(String[] args) {
Scanner scan = new Scanner(System.in);
int t = scan.nextInt(); // 读取测试用例的数量
solve(); // 预处理所有可能的 dp 值
while (t-- > 0) { // 处理每个测试用例
int n = scan.nextInt(); // 读取棋盘大小
System.out.println(dp[n]); // 输出结果
}
scan.close(); // 关闭 Scanner
}
}
示例运行:
假设输入:
2
3
4
- 处理过程:
-
- 预处理
dp数组:
- 预处理
-
-
dp[0] = 1dp[1] = 1dp[2] = (dp[1] + 1 * dp[0]) % MOD = (1 + 1 * 1) % MOD = 2dp[3] = (dp[2] + 2 * dp[1]) % MOD = (2 + 2 * 1) % MOD = 4dp[4] = (dp[3] + 3 * dp[2]) % MOD = (4 + 3 * 2) % MOD = 10
-
-
- 输出结果:
-
-
- 对于 ( 3 \times 3 ) 的棋盘,方法数为
4。 - 对于 ( 4 \times 4 ) 的棋盘,方法数为
10。
- 对于 ( 3 \times 3 ) 的棋盘,方法数为
-
输出:
4
10
考点分析:
- 动态规划:
-
- 状态定义:
dp[k]表示 ( k \times k ) 矩阵的对称放置方法数。 - 状态转移方程:
dp[k] = dp[k-1] + (k-1) * dp[k-2]。 - 初始条件:
dp[0] = 1,dp[1] = 1。
- 状态定义:
- 模运算:
-
- 由于结果可能非常大,需要对结果取模(模数为 ( 10^9 + 7 ))。
- 预处理:
-
- 在程序开始时,预先计算并存储所有可能的 ( k ) 值的
dp[k],这样在处理多个查询时可以直接输出结果,提高效率。
- 在程序开始时,预先计算并存储所有可能的 ( k ) 值的
- 循环和条件语句:
-
- 使用循环和条件语句处理多个测试用例。
元宵交友 二分查找
import java.util.Arrays;
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner scan = new Scanner(System.in);
int n = scan.nextInt(); // 朋友的数量
int[] friends = new int[n]; // 存储每个朋友的到达时间
for (int i = 0; i < n; i++) {
friends[i] = scan.nextInt(); // 读取每个朋友的到达时间
}
Arrays.sort(friends); // 对到达时间进行升序排序
// 对于每个可能的最小时间间隔 k(从 1 到 n)
for (int k = 1; k <= n; k++) {
int cnt = 0; // 计数器,记录可以邀请的朋友数量
int i = 0; // 当前考虑的朋友索引
while (true) {
cnt++; // 当前朋友可以邀请
// 使用二分查找找到下一个可以邀请的朋友
int index = binarySearch(friends, i + 1, n - 1, friends[i] + k);
if (index >= n) {
break; // 如果没有找到,结束循环
}
i = index; // 更新当前考虑的朋友索引
}
System.out.print(cnt + " "); // 输出当前 k 下可以邀请的朋友数量
}
scan.close(); // 关闭 Scanner
}
// 二分查找
public static int binarySearch(int[] arr, int left, int right, int target) {
int l = left;
int r = right;
while (l <= r) {
int mid = l + ((r - l) >> 1); // 计算中间位置
if (arr[mid] >= target) {
r = mid - 1; // 如果中间值大于等于目标值,向左半部分查找
} else {
l = mid + 1; // 否则,向右半部分查找
}
}
return l; // 返回目标值应该插入的位置
}
}
灯笼大乱斗 区间重叠 分段处理
问题描述:
- 输入:
-
- 一个整数 (n),表示数据的长度。
- 两组数据 (A) 和 (B),每组包含 (n) 个整数。
- 输出:
-
- 输出满足条件的区间对的数量。
条件说明:
- 对于每个位置 (i),定义两个新的数组 (C) 和 (D):
-
- (C[i] = A[i] - B[i])
- (D[i] = A[i] + B[i])
- 一个区间 ([i, j]) 满足条件,当且仅当对于所有 (k)((i \leq k \leq j)),满足 (C[k] \leq C[k+1]) 和 (D[k] \leq D[k+1])。
解题思路:
- 预处理:
-
- 计算两个数组 (C) 和 (D),分别表示每个位置的 (A[i] - B[i]) 和 (A[i] + B[i])。
- 分段处理:
-
- 遍历数组 (C) 和 (D),找到满足条件的连续区间。
- 使用一个列表
segments来存储每个连续区间的长度。
- 计算结果:
-
- 对于每个长度为 (k) 的连续区间,满足条件的区间对数量为 (\frac{k \times (k - 1)}{2})。
- 累加所有区间的区间对数量,得到最终结果。
代码详细注释:
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.List;
public class Main {
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
int n = Integer.parseInt(br.readLine()); // 读取数据长度
if (n < 2) {
System.out.println(0); // 如果长度小于2,直接输出0
return;
}
long[] A = new long[n]; // 存储第一组数据
long[] B = new long[n]; // 存储第二组数据
// 读取第一组数据
String[] aStrs = br.readLine().split(" ");
// 读取第二组数据
String[] bStrs = br.readLine().split(" ");
// 将字符串数组转换为长整型数组
for (int i = 0; i < n; i++) {
A[i] = Long.parseLong(aStrs[i]);
B[i] = Long.parseLong(bStrs[i]);
}
// 计算C和D数组
long[] C = new long[n];
long[] D = new long[n];
for (int i = 0; i < n; i++) {
C[i] = A[i] - B[i]; // C[i] = A[i] - B[i]
D[i] = A[i] + B[i]; // D[i] = A[i] + B[i]
}
// 分段处理
List<Integer> segments = new ArrayList<>();
int currentLength = 1; // 当前连续区间的长度
for (int i = 1; i < n; i++) {
// 如果当前区间满足条件,增加当前区间的长度
if (C[i] > C[i - 1] && D[i] > D[i - 1]) {
currentLength++;
} else {
// 否则,将当前区间的长度添加到列表中,并重置当前区间的长度
segments.add(currentLength);
currentLength = 1;
}
}
// 添加最后一个区间的长度
segments.add(currentLength);
// 计算结果
long total = 0;
for (int k : segments) {
if (k >= 2) {
// 对于长度为k的区间,满足条件的区间对数量为k * (k - 1) / 2
total += (long) k * (k - 1) / 2;
}
}
System.out.println(total); // 输出最终结果
}
}
示例运行:
假设输入:
5
1 2 3 4 5
1 1 1 1 1
- 处理过程:
-
- (A = [1, 2, 3, 4, 5])
- (B = [1, 1, 1, 1, 1])
- (C = [0, 1, 2, 3, 4])
- (D = [2, 3, 4, 5, 6])
- 遍历 (C) 和 (D),发现整个数组都满足条件,因此只有一个长度为 5 的连续区间。
- 计算结果:(\frac{5 \times (5 - 1)}{2} = 10)
输出:
10
考点分析:
- 数组操作:
-
- 读取和处理两组数据。
- 计算两个新的数组 (C) 和 (D)。
- 分段处理:
-
- 使用一个列表
segments来存储每个连续区间的长度。 - 遍历数组,找到满足条件的连续区间。
- 使用一个列表
- 组合数学:
-
- 对于长度为 (k) 的连续区间,满足条件的区间对数量为 (\frac{k \times (k - 1)}{2})。
- 输入输出:
-
- 使用
BufferedReader读取输入,提高效率。 - 输出最终结果。
- 使用
注意事项:
- 边界条件:确保处理 (n < 2) 的情况。
- 数组索引:在遍历时,确保索引不会越界。
- 性能优化:通过分段处理,避免暴力搜索,提高程序的效率。