首页 > 基础资料 博客日记
2024 第十五届蓝桥杯 Java A 组:全题解
2024-05-13 18:00:08基础资料围观325次
目前,C ~ F 题已经在 dotcpp 取得 AC:
A. 拼正方形
题目描述
有 7385137888721
个
2
×
2
2 \times 2
2×2 的方块和 10470245
个
1
×
1
1 \times 1
1×1 的方块,求能拼出的正方形的最大边长。
解题思路
用 1 × 1 1 \times 1 1×1 的方块给已经用 2 × 2 2 \times 2 2×2 的方块拼成的大正方形“镶边”,每次使其边长加 1 1 1。
public class Main {
public static void main(String[] args) {
final long SQ2 = 7385137888721L;
final long SQ1 = 10470245L;
// 用 2x2 的方块拼成一个大正方形
long ans = (long) Math.sqrt(SQ2) * 2;
// 用 1x1 的方块镶边
long sq1 = SQ1;
while (true) {
if ((sq1 -= ans * 2 + 1) < 0) break;
ans++;
}
System.out.println(ans);
}
}
输出:
5435122
答案
5435122
B. 召唤数学精灵
题目描述
计算 i i i 的数量,满足:
1
≤ i ≤ \leq i \leq ≤i≤2024041331404202
- A ( i ) − B ( i ) m o d 100 = 0 A(i) - B(i) \space {\rm mod} \space 100 = 0 A(i)−B(i) mod 100=0
其中, A ( x ) = s u m ( 1 , x ) A(x) = {\rm sum}(1,\space x) A(x)=sum(1, x), B ( x ) = x ! B(x) = x! B(x)=x!。
解题思路
观察到 ∀ i ≥ 100 \forall i \geq 100 ∀i≥100, B ( i ) m o d 100 = 0 B(i) \space {\rm mod} \space 100 = 0 B(i) mod 100=0(赛后发现 ∀ i ≥ 10 \forall i \geq 10 ∀i≥10 即可),那么算式 A ( i ) − B ( i ) m o d 100 = 0 A(i) - B(i) \space {\rm mod} \space 100 = 0 A(i)−B(i) mod 100=0 就化简为 A ( i ) m o d 100 = 0 A(i) \space {\rm mod} \space 100 = 0 A(i) mod 100=0。
考虑 s u m ( 1 , 200 ) = 20100 {\rm sum}(1,\space 200) = 20100 sum(1, 200)=20100, 20100 m o d 100 = 0 20100 \space {\rm mod} \space 100 = 0 20100 mod 100=0,可以以 200 200 200 为一个周期,计算一个周期内 A ( i ) m o d 100 = 0 A(i) \space {\rm mod} \space 100 = 0 A(i) mod 100=0 的次数,与周期数相乘。
将 1
~ 2024041331404202
拆成 1
~ 200
和 201
~ 2024041331404202
两部分:
- 第一部分的特殊之处在于 i = 1 i = 1 i=1 和 i = 3 i = 3 i=3,它们都满足要求,而之后的例如 i = 201 i = 201 i=201 和 i = 203 i = 203 i=203 都不满足要求,因此额外加 2 2 2。
- 第二部分以每 200 200 200 一个周期,算得周期内有 4 4 4 个 i i i 满足要求(代码略),乘以总周期数。
public class Main {
public static void main(String[] args) {
final long ed = 2024041331404202L;
// 分别计算两部分答案
long ans1 = 4 + 2;
long ans2 = (ed - 200) / 200 * 4;
System.out.println(ans1 + ans2);
}
}
输出:
40480826628086
答案
40480826628086
C. 数字诗意
题目描述
输入 n
个数字,输出其中无法被表示为连续多个(至少 2 个)正整数之和的数字个数。
输入输出示例
输入共 2 行,第 1 行含一个正整数 n
,第 2 行含 n
个正整数 x
:
3
3 6 8
范围:
-
1
≤
1 \leq
1≤
n
≤ 2 × 1 0 5 \leq 2 \times 10^5 ≤2×105 -
1
≤
1 \leq
1≤
x
≤ 1 × 1 0 16 \leq 1 \times 10^{16} ≤1×1016
输出一个整数表示答案:
1
解释:
3 = 1 + 2
6 = 1 + 2 + 3
8
无法表示为连续多个正整数之和
解题思路
手写 / 暴力打一个小表,下划线 _
表示无法表出的数字:
_ _ 3 _ 5
6 7 _ 9 10
11 12 13 14 15
_ 17 18 19 20
找规律,发现只有 2 n 2^n 2n 无法表出。
记 long
内最大的
2
n
2^n
2n 为 POW
,对输入的每个数 x
,POW % x == 0
时答案加 1。
答案
import java.util.*;
import java.io.*;
public class Main {
public static void main(String[] args) throws IOException {
BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
PrintWriter out = new PrintWriter(System.out);
final long POW = 1L << 62;
int n = Integer.parseInt(in.readLine());
StringTokenizer st = new StringTokenizer(in.readLine());
int ans = 0;
while (n-- > 0) {
if (POW % Long.parseLong(st.nextToken()) == 0) ans++;
}
out.println(ans);
out.flush();
out.close();
}
}
D. 回文数组
题目描述
输入一个长为 n
的数组,输出使其变得回文的最少操作次数。
在 1 次操作中,可以二选一:
- 选择一个元素,对其增减 1 1 1
- 选择相邻的两个元素,对其增减 1 1 1
输入输出示例
输入共 2 行,第 1 行含一个正整数 n
,第 2 行含 n
个正整数 x
:
4
1 2 3 4
范围:
-
1
⩽
1 \leqslant
1⩽
n
⩽ 1 × 1 0 5 \leqslant 1 \times 10^5 ⩽1×105 -
−
1
×
1
0
6
⩽
-1 \times 10^6 \leqslant
−1×106⩽
x
⩽ 1 × 1 0 6 \leqslant 1 \times 10^6 ⩽1×106
输出一个整数表示答案:
3
解释:
- 第 1 次操作后:
[2, 3, 3, 4]
- 第 2 次操作后:
[3, 3, 3, 4]
- 第 3 次操作后:
[4, 3, 3, 4]
,已经是回文
解题思路
双指针 i
、j
从两端向中间看,每次先用“操作 1”使 arr[i] == arr[j]
,然后看 arr[i+1]
和 arr[j-1]
是否与它们同号,如果同号,可以将一部分操作改为“操作 2”,从而节约操作次数。
实现时,可以先一次遍历,更新 arr[i] -= arr[j]
,再次遍历时仅比较 arr[i]
和 arr[i+1]
是否同号即可。
答案
import java.util.*;
import java.io.*;
public class Main {
public static void main(String[] args) throws IOException {
BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
PrintWriter out = new PrintWriter(System.out);
int n = Integer.parseInt(in.readLine());
int[] arr = new int[n];
StringTokenizer st = new StringTokenizer(in.readLine());
for (int i = 0; i < n; i++) {
arr[i] = Integer.parseInt(st.nextToken());
}
// 预处理
for (int i = 0, j = n - 1; i < j; i++, j--) {
arr[i] -= arr[j];
}
// 遍历前一半
long ans = 0;
for (int i = 0; i < n / 2; i++) {
if (arr[i] == 0) continue;
// 先用 "操作 1" 变 arr[i]
ans += Math.abs(arr[i]);
// 再看能否顺便变一下 arr[i+1]
if (i + 1 < n / 2 && arr[i] > 0 == arr[i + 1] > 0) {
arr[i + 1] = Math.abs(arr[i]) > Math.abs(arr[i + 1]) ? 0 : arr[i + 1] - arr[i];
}
}
out.println(ans);
out.flush();
out.close();
}
}
E. 吊坠
题目描述
输入 n
个长为 m
的字符串,作为图的结点,每两个结点间都有一条带权边,权值为两字符串的最长公共 子串 的长度。
注意,本题中的字符串都可以旋转,如 "abba"
可以旋转变换为 "aabb"
,从而 "abba"
和 "acca"
之间的边权值为 2
。
选出 最少 的边让所有结点连通,且成本 最大,输出这个成本。
输入输出示例
输入共 n + 1
行,第 1 行含两个正整数 n
、m
,随后 n
行各含一个长为 m
的字符串:
4 4
aabb
abba
acca
abcd
范围:
-
1
⩽
1 \leqslant
1⩽
n
⩽ 200 \leqslant 200 ⩽200 -
1
⩽
1 \leqslant
1⩽
x
⩽ 50 \leqslant 50 ⩽50
输出一个整数表示答案:
8
解释:连接 <1, 2>
,<2, 3>
,<2, 4>
,边权和为
4
+
2
+
2
=
8
4 + 2 + 2 = 8
4+2+2=8。
解题思路
分 2 大步:
- 求出所有边的边权,如:
[0, 4, 2, 2]
[0, 0, 2, 2]
[0, 0, 0, 1]
[0, 0, 0, 0]
本题中的字符串可旋转,考虑在每个字符串后重复一次自身,如 "abcd"
变为 "abcdabcd"
,这样遍历每个长为 m
的窗口就得到了自身的全部旋转结果。
用动态规划,对两个字符串滑一次,求得它们的最长公共子串长度,即边权。
- 用 kruskal 算法——原本求最小生成树的算法——求 最大生成树。
所有边入堆,并查集判环,无环时加入边并 union()
。
答案
import java.util.*;
import java.io.*;
public class Main {
private static char[][] arr;
public static void main(String[] args) throws IOException {
BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
PrintWriter out = new PrintWriter(System.out);
StringTokenizer st = new StringTokenizer(in.readLine());
int n = Integer.parseInt(st.nextToken());
int m = Integer.parseInt(st.nextToken());
// 每个字符串重复自己
arr = new char[n][m * 2];
for (int i = 0; i < n; i++) {
char[] s = in.readLine().toCharArray();
for (int j = 0; j < m; j++) {
arr[i][j] = arr[i][j + m] = s[j];
}
}
// 求所有边权
int[][] dist = new int[n][n];
for (int i = 0; i < n - 1; i++) {
for (int j = i + 1; j < n; j++) {
dist[i][j] = lcs(i, j);
}
}
// 所有边入堆
Queue<int[]> pq = new PriorityQueue<>(Comparator.comparingInt((int[] a) -> -a[2]));
for (int i = 0; i < n - 1; i++) {
for (int j = i + 1; j < n; j++) {
pq.offer(new int[]{i, j, dist[i][j]});
}
}
// Kruskal 算法
int[] root = new int[n];
Arrays.setAll(root, a -> a);
int take = 0;
int ans = 0;
while (take < n - 1) {
int[] p = pq.poll();
if (find(root, p[0]) != find(root, p[1])) {
union(root, p[0], p[1]);
take++;
ans += p[2];
}
}
out.println(ans);
out.flush();
out.close();
}
// 求两旋转字符串的最长公共子串长度
private static int lcs(int x, int y) {
int m = arr[0].length;
int max = 0;
int[][] dp = new int[m + 1][m + 1];
for (int i = 1; i <= m; i++) {
for (int j = 1; j <= m; j++) {
if (arr[x][i - 1] == arr[y][j - 1]) {
dp[i][j] = Math.max(dp[i - 1][j - 1] + 1, dp[i][j]);
max = Math.max(dp[i][j], max);
}
}
}
return Math.min(max, m / 2);
}
// 并查集
private static int find(int[] root, int i) {
return root[i] == i ? i : (root[i] = find(root, root[i]));
}
// 并查集
private static void union(int[] root, int x, int y) {
root[find(root, x)] = find(root, y);
}
}
F. 砍柴
题目描述
A、B 两人轮流砍柴,轮到某一方砍柴时,设原木剩余长度为 x
:
- 如果
x
为 0 0 0 或 1 1 1,本方无法继续砍柴,从而输掉比赛。 - 如果
x
⩾ 2 \geqslant 2 ⩾2,本方可选择一个长度 2 ⩽ 2 \leqslant 2⩽p
⩽ \leqslant ⩽x
,砍掉长为p
的一段。
共 n
段原木(n
局游戏),每局 A 先手,双方都采用最佳策略。
对每局游戏,如果 A 能够取胜,输出 1
,否则输出 0
。
输入输出示例
输入共 n + 1
行,第 1 行含一个正整数 n
,随后 n
行各含一个正整数 x
:
3
1
2
6
范围:
-
1
⩽
1 \leqslant
1⩽
n
⩽ 1 0 4 \leqslant 10^4 ⩽104 -
1
⩽
1 \leqslant
1⩽
x
⩽ 1 0 5 \leqslant 10^5 ⩽105
输出 n
行,每行含一个整数 0
或 1
:
0
1
1
解释:
- 对
x
= 1 = 1 =1,A 已无法砍柴,A 失败。 - 对
x
= 2 = 2 =2,A 砍掉p
= 2 = 2 =2,轮到 B 时x
= 0 = 0 =0,已无法砍柴,A 获胜。 - 对
x
= 6 = 6 =6,A 砍掉p
= 5 = 5 =5,轮到 B 时x
= 1 = 1 =1,已无法砍柴,A 获胜。
解题思路
典型的博弈问题,预处理范围内的质数集,boolean win(int x)
用 DFS 判断当前剩余长度能否获胜,递归交换对手。
剪枝:对 win(x)
,二分质数集查询
⩽
\leqslant
⩽ x
的最大位置,倒序遍历,更快接近 DFS 基态,否则
1
0
5
10^5
105 容易栈溢出。
答案
import java.util.*;
import java.io.*;
public class Main {
private static int[] primes;
private static int[] memo;
public static void main(String[] args) throws IOException {
BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
PrintWriter out = new PrintWriter(System.out);
int n = Integer.parseInt(in.readLine());
int[] arr = new int[n];
// 取输入的最大值
int max = 0;
for (int i = 0; i < n; i++) {
arr[i] = Integer.parseInt(in.readLine());
max = Math.max(arr[i], max);
}
// 预处理质数
getPrimes(max);
memo = new int[max + 1];
for (int x : arr) {
out.println(win(x) ? 1 : 0);
}
out.flush();
out.close();
}
// 筛法预处理得到范围内所有质数
private static void getPrimes(int max) {
boolean[] v = new boolean[max + 1];
Arrays.fill(v, true);
for (int x = 2; x <= max; x++) {
if (!v[x]) continue;
for (int y = x * 2; y <= max; y += x) {
v[y] = false;
}
}
int n = 0;
for (int x = 2; x <= max; x++) {
if (v[x]) n++;
}
primes = new int[n];
int i = 0;
for (int x = 2; x <= max; x++) {
if (v[x]) primes[i++] = x;
}
}
// DFS 博弈
private static boolean win(int x) {
if (memo[x] != 0) return memo[x] == 1;
if (x <= 1) return false;
// 剪枝: 倒序遍历质数集, 更快接近 DFS 基态
for (int i = bis(x); i >= 0; i--) {
if (!win(x - primes[i])) {
memo[x] = 1;
return true;
}
}
memo[x] = -1;
return false;
}
// 二分, 从质数集中查找 <= x 的最大位置
private static int bis(int x) {
int l = 0;
int r = primes.length - 1;
while (l <= r) {
int m = l + r >> 1;
if (primes[m] <= x) l++;
else r--;
}
return r;
}
}
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:jacktools123@163.com进行投诉反馈,一经查实,立即删除!
标签: