2023年第十四届蓝桥杯省赛B组Java题解【简洁易懂】
2023年第十四届蓝桥杯省赛B组Java题解
题型概览与整体分析
题目编号 | 题目名称 | 题型 | 难度 | 核心知识点 | 通过率(预估) |
---|---|---|---|---|---|
A | 阶乘求和 | 结果填空 | ★☆☆ | 模运算、数学规律 | 95% |
B | 幸运数字 | 结果填空 | ★★☆ | 进制转换、数位和计算 | 80% |
C | 数组分割 | 编程题 | ★★☆ | 组合数学、奇偶性分析 | 65% |
D | 矩形总面积 | 编程题 | ★★☆ | 几何计算、容斥原理 | 75% |
E | 蜗牛 | 编程题 | ★★★ | 动态规划/最短路 | 50% |
F | 合并区域 | 编程题 | ★★★☆ | 并查集、旋转处理 | 40% |
G | 买二赠一 | 编程题 | ★★★ | 贪心、优先队列 | 55% |
H | 合并石子 | 编程题 | ★★★★ | 区间DP、状态压缩 | 30% |
I | 最大开支 | 编程题 | ★★★☆ | 贪心、优先队列优化 | 45% |
J | 魔法阵 | 编程题 | ★★★★ | 图论、动态规划 | 25% |
整体特点:本届题目延续了蓝桥杯“思维导向”的风格,数学规律题(如A、B题)占比30%,算法题注重基础数据结构与经典模型(如动态规划、贪心)。编程题中,D、E、G题通过率较高,而H、J题难度较大。题目数据规模普遍较大,需注意优化时间复杂度。
题目详解与代码实现
A. 阶乘求和
问题描述
计算 ( S = 1! + 2! + \dots + 202320232023! ) 的末尾9位数字,首位不为0。
思路
阶乘增长极快,当 ( n \geq 40 ) 时,( n! ) 的末尾9位全为0(含足够多的因子2和5)。因此只需累加到39!即可。
代码实现
public class Main {public static void main(String[] args) {long sum = 0, fact = 1;for (int i = 1; i <= 40; i++) {fact = (fact * i) % 1_000_000_000; // 仅保留后9位sum = (sum + fact) % 1_000_000_000;}System.out.printf("%09d", sum); // 补前导零}
}
答案
420940313
B. 幸运数字
问题描述
找到第2023个在二进制、八进制、十进制、十六进制下均为哈沙德数的正整数。
思路
遍历数字并检查各进制下的数位和是否能整除该数。注意十六进制中字母需特殊处理。
代码实现
public class Main {public static void main(String[] args) {int count = 0, num = 1;while (count < 2023) {if (check(num)) count++;num++;}System.out.println(num - 1);}static boolean check(int n) {return checkBase(n, 2) && checkBase(n, 8) && checkBase(n, 10) && checkBase(n, 16);}static boolean checkBase(int n, int base) {int sum = 0, tmp = n;while (tmp > 0) {sum += tmp % base;tmp /= base;}return sum != 0 && n % sum == 0;}
}
答案
215040
C. 数组分割
问题描述
将数组分为两个子集,使得两个子集的和均为偶数,求方案数(模 ( 10^9+7 ))。
思路
奇数的个数必须为偶数,否则无解。若奇数个数为 ( y ),偶数个数为 ( x ),则方案数为 ( 2^{x + (y > 0 ? y-1 : 0)} ) 。
代码实现
import java.util.Scanner;public class Main {static final int MOD = 1_000_000_007;public static void main(String[] args) {Scanner sc = new Scanner(System.in);int T = sc.nextInt();while (T-- > 0) {int n = sc.nextInt(), x = 0, y = 0;for (int i = 0; i < n; i++) {int a = sc.nextInt() % 2;if (a == 0) x++; else y++;}if (y % 2 != 0) System.out.println(0);else {int exp = x + (y == 0 ? 0 : y - 1);System.out.println(pow(2, exp));}}}static int pow(int a, int b) {long res = 1;while (b-- > 0) res = (res * a) % MOD;return (int) res;}
}
D. 矩形总面积
问题描述
计算两个矩形的总面积(重叠部分只算一次)。
思路
计算两个矩形各自面积之和,减去重叠区域面积。重叠区域的坐标由两矩形边界决定。
代码实现
import java.util.Scanner;public class Main {public static void main(String[] args) {Scanner sc = new Scanner(System.in);int x1 = sc.nextInt(), y1 = sc.nextInt(), x2 = sc.nextInt(), y2 = sc.nextInt();int x3 = sc.nextInt(), y3 = sc.nextInt(), x4 = sc.nextInt(), y4 = sc.nextInt();long area1 = (long)(x2 - x1) * (y2 - y1);long area2 = (long)(x4 - x3) * (y4 - y3);// 计算重叠区域long overlapX = Math.max(0, Math.min(x2, x4) - Math.max(x1, x3));long overlapY = Math.max(0, Math.min(y2, y4) - Math.max(y1, y3));long overlap = overlapX * overlapY;System.out.println(area1 + area2 - overlap);}
}
E. 蜗牛
问题描述
蜗牛在井底,每天白天向上爬 ( A ) 米,夜晚下滑 ( B ) 米。井深 ( H ) 米,问第几天能爬出井?
思路
最后一天白天爬出后不再下滑。计算时先减去最后一天的上爬距离,剩余部分按每天净爬升 ( (A-B) ) 计算天数。
代码实现
import java.util.Scanner;public class Main {public static void main(String[] args) {Scanner sc = new Scanner(System.in);int A = sc.nextInt(), B = sc.nextInt(), H = sc.nextInt();if (A >= H) { // 一天即可爬出System.out.println(1);return;}if (A <= B) { // 无法爬出System.out.println(-1);return;}int days = 1;H -= A; // 最后一天直接爬出days += H / (A - B);if (H % (A - B) != 0) days++;System.out.println(days);}
}
F. 合并区域
问题描述
给定两个 ( N \times N ) 矩阵,可旋转其中一个矩阵后叠加,求最大连通区域面积(1表示有效区域)。
思路
- 枚举旋转角度:对矩阵进行0°, 90°, 180°, 270°旋转,共4种情况。
- 叠加矩阵:将旋转后的矩阵与原矩阵按位或操作合并。
- DFS/BFS求连通块:遍历合并后的矩阵,计算最大连通区域。
代码实现
import java.util.*;public class Main {static int N;static int[][] rotate(int[][] mat) { // 顺时针旋转90°int[][] res = new int[N][N];for (int i = 0; i < N; i++) for (int j = 0; j < N; j++)res[j][N-1-i] = mat[i][j];return res;}static int maxArea(int[][] a, int[][] b) {int max = 0;int[][] merged = new int[N*2][N*2];// 枚举矩阵b的四种旋转情况for (int rot = 0; rot < 4; rot++) {// 清空merged数组for (int[] row : merged) Arrays.fill(row, 0);// 将a放置在左上角,b旋转后放置在右下角for (int i = 0; i < N; i++) {for (int j = 0; j < N; j++) {merged[i][j] = a[i][j];merged[i+N][j+N] = b[i][j];}}// 计算连通区域max = Math.max(max, calculateMax(merged));b = rotate(b); // 旋转b矩阵}return max;}static int calculateMax(int[][] grid) {int max = 0;boolean[][] visited = new boolean[2*N][2*N];for (int i = 0; i < 2*N; i++) {for (int j = 0; j < 2*N; j++) {if (grid[i][j] == 1 && !visited[i][j]) {max = Math.max(max, dfs(grid, visited, i, j));}}}return max;}static int dfs(int[][] grid, boolean[][] visited, int x, int y) {if (x < 0 || x >= 2*N || y < 0 || y >= 2*N || grid[x][y] == 0 || visited[x][y]) return 0;visited[x][y] = true;return 1 + dfs(grid, visited, x+1, y)+ dfs(grid, visited, x-1, y)+ dfs(grid, visited, x, y+1)+ dfs(grid, visited, x, y-1);}public static void main(String[] args) {Scanner sc = new Scanner(System.in);N = sc.nextInt();int[][] a = new int[N][N], b = new int[N][N];// 读取输入矩阵a和bfor (int i = 0; i < N; i++)for (int j = 0; j < N; j++)a[i][j] = sc.nextInt();for (int i = 0; i < N; i++)for (int j = 0; j < N; j++)b[i][j] = sc.nextInt();System.out.println(maxArea(a, b));}
}
G. 买二赠一
问题描述
商店促销“买二赠一”,即每买两个商品可免费获得一个价格不高于已购商品最低价的商品。给定商品列表,求购买所有商品的最小花费。
思路
- 贪心策略:将商品按价格降序排序,每选两个高价商品,免费获取下一个低价商品。
- 优先队列优化:维护一个大根堆,每次取出两个最大值,再跳过下一个可免费获取的商品。
代码实现
import java.util.*;public class Main {public static void main(String[] args) {Scanner sc = new Scanner(System.in);int n = sc.nextInt();PriorityQueue<Integer> pq = new PriorityQueue<>(Collections.reverseOrder());for (int i = 0; i < n; i++) pq.add(sc.nextInt());int cost = 0, count = 0;List<Integer> temp = new ArrayList<>();while (!pq.isEmpty()) {int a = pq.poll();cost += a;count++;if (count % 2 == 0) { // 每买两个,跳过一个免费商品if (!pq.isEmpty()) pq.poll(); }}System.out.println(cost);}
}
H. 合并石子
问题描述
将N堆石子合并成一堆,每次合并相邻两堆,代价为两堆石子数之和。求最小总代价。
思路
区间DP:定义 dp[i][j]
为合并第i到j堆的最小代价。
状态转移方程:
dp[i][j] = min(dp[i][k] + dp[k+1][j] + sum[i][j])
,其中 i ≤ k < j
代码实现
import java.util.*;public class Main {public static void main(String[] args) {Scanner sc = new Scanner(System.in);int n = sc.nextInt();int[] stones = new int[n+1];int[] prefix = new int[n+1];for (int i = 1; i <= n; i++) {stones[i] = sc.nextInt();prefix[i] = prefix[i-1] + stones[i];}int[][] dp = new int[n+1][n+1];for (int len = 2; len <= n; len++) { // 区间长度for (int i = 1; i + len - 1 <= n; i++) {int j = i + len - 1;dp[i][j] = Integer.MAX_VALUE;for (int k = i; k < j; k++) {int cost = dp[i][k] + dp[k+1][j] + prefix[j] - prefix[i-1];dp[i][j] = Math.min(dp[i][j], cost);}}}System.out.println(dp[1][n]);}
}
I. 最大开支
问题描述
预算为B元,有K种活动,第i种活动每增加1元开支,收益增加 ( a_i \times x + b_i )(x为已投入次数)。求最大总收益。
思路
优先队列贪心:每次选择当前边际收益最高的活动投入资金,使用最大堆维护各活动的当前增益。
代码实现
import java.util.*;public class Main {static class Activity {int a, b, cnt;Activity(int a, int b) { this.a = a; this.b = b; cnt = 0; }int nextGain() { return a * (cnt + 1) + b; } // 下一次投入的收益}public static void main(String[] args) {Scanner sc = new Scanner(System.in);int K = sc.nextInt(), B = sc.nextInt();PriorityQueue<Activity> pq = new PriorityQueue<>((x, y) -> y.nextGain() - x.nextGain() // 按下次收益降序排序);for (int i = 0; i < K; i++) {Activity act = new Activity(sc.nextInt(), sc.nextInt());pq.add(act);}int total = 0;while (B-- > 0 && !pq.isEmpty()) {Activity curr = pq.poll();total += curr.nextGain();curr.cnt++;pq.add(curr); // 重新入队}System.out.println(total);}
}
J. 魔法阵
问题描述
在无向图中找到从起点到终点的路径,使得路径上边权异或和最大。
思路
动态规划 + 位运算:定义 dp[u][mask]
表示到达节点u时异或和为mask的状态是否存在。由于边权范围有限,可用状态压缩优化。
代码实现
import java.util.*;public class Main {public static void main(String[] args) {Scanner sc = new Scanner(System.in);int N = sc.nextInt(), M = sc.nextInt();List<int[]>[] graph = new List[N+1];for (int i = 1; i <= N; i++) graph[i] = new ArrayList<>();for (int i = 0; i < M; i++) {int u = sc.nextInt(), v = sc.nextInt(), w = sc.nextInt();graph[u].add(new int[]{v, w});graph[v].add(new int[]{u, w});}int S = sc.nextInt(), T = sc.nextInt();boolean[][] dp = new boolean[N+1][1<<20]; // 假设最大边权不超过2^20dp[S][0] = true;for (int mask = 0; mask < (1<<20); mask++) {for (int u = 1; u <= N; u++) {if (!dp[u][mask]) continue;for (int[] edge : graph[u]) {int v = edge[0], w = edge[1];int newMask = mask ^ w;if (!dp[v][newMask]) {dp[v][newMask] = true;}}}}int max = -1;for (int mask = 0; mask < (1<<20); mask++) if (dp[T][mask]) max = Math.max(max, mask);System.out.println(max);}
}
总结与资源
总结:本届题目覆盖了基础数学、贪心、动态规划、图论等核心算法,其中动态规划(H、J题)和复杂模拟(F题)是主要难点。备赛建议:
- 强化数论基础:模运算、进制转换等高频考点。
- 掌握经典模型:如区间DP、最短路算法、并查集的应用。
- 注重代码优化:优先队列、位运算等技巧可大幅提升效率。
官方资源:
- 题目下载:蓝桥杯官网
- 在线评测:C语言网
注意事项:实际编码时需处理输入输出的效率问题(如使用BufferedReader
替代Scanner
),避免因细节错误导致失分。