第11届蓝桥杯JavaB组省赛
第11届蓝桥杯JavaB组省赛
其他链接
第10届蓝桥杯JavaB组省赛 - Cattle_Horse
第12届蓝桥杯JavaB组省赛 - Cattle_Horse
第13届蓝桥杯javaB组省赛 - Cattle_Horse
前言
用时及分数
自测用时:约 \(2\) 小时
最终得分:\(55\) 分
PS:此处得分依据蓝桥杯官网 \(OJ\) 测得
题目情况
完成题目:\(ABCFG\)
\(D\) 题的题面没看完全就写答案了,不清楚如何根据给出边判断图是否连通
\(H\) 数字三角形,未想出如何使向左向右步数相差不超过 \(1\),应该直接考虑结果哪个位置
试题 A 门牌制作
问题描述
小蓝要为一条街的住户制作门牌号。
这条街一共有 \(2020\) 位住户,门牌号从 \(1\) 到 \(2020\) 编号。
小蓝制作门牌的方法是先制作 \(0\) 到 \(9\) 这几个数字字符,最后根据需要将字符粘贴到门牌上,例如门牌 \(1017\) 需要依次粘贴字符 \(1、0、1、7\),即需要 1 个 字符 \(0\),\(2\) 个字符 \(1\),\(1\) 个字符 \(7\)。
请问要制作所有的 \(1\) 到 \(2020\) 号门牌,总共需要多少个字符 \(2\)?
答案:\(624\)
Code
public class Main {
static int ans = 0;
static void solve(int t) {
while (t != 0) {
if (t % 10 == 2) ++ans;
t /= 10;
}
}
public static void main(String[] args) {
for (int i = 1; i <= 2020; ++i) {
solve(i);
}
System.out.println(ans);//624
}
}
试题 B 寻找 2020
问题描述
小蓝有一个数字矩阵,里面只包含数字 \(0\) 和 \(2\)。小蓝很喜欢 \(2020\),他想找到这个数字矩阵中有多少个 \(2020\)。
小蓝只关注三种构成 \(2020\) 的方式:
-
同一行里面连续四个字符从左到右构成 \(2020\)。
-
同一列里面连续四个字符从上到下构成 \(2020\)。
-
在一条从左上到右下的斜线上连续四个字符,从左上到右下构成 \(2020\)。
例如,对于下面的矩阵:
220000
000000
002202
000000
000022
002020
一共有 \(5\) 个 \(2020\)。其中 \(1\) 个是在同一行里的,\(1\) 个是在同一列里的,\(3\) 个 是斜线上的。小蓝的矩阵比上面的矩阵要大,由于太大了,他只好将这个矩阵放在了一个文件里面,在试题目录下有一个文件 2020.txt,里面给出了小蓝的矩阵。
请帮助小蓝确定在他的矩阵中有多少个 \(2020\)。
答案:\(16520\)
Code
import java.io.*;
public class Main {
static String[] a = new String[10000];
static int ans = 0, cnt = 0;
static void solve(int x, int y, int offsetX, int offsetY) {
if (x + 3 * offsetX < 0 || x + 3 * offsetX >= cnt) return;
if (y + 3 * offsetY < 0 || y + 3 * offsetY >= a[x + 3 * offsetX].length()) return;
if (a[x].charAt(y) == '2' && a[x + offsetX].charAt(y + offsetY) == '0'
&& a[x + 2 * offsetX].charAt(y + 2 * offsetY) == '2'
&& a[x + 3 * offsetX].charAt(y + 3 * offsetY) == '0')
++ans;
}
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
// BufferedReader br=new BufferedReader(new InputStreamReader(new FileInputStream("2020.txt")));
String line;
while ((line = br.readLine()) != null) a[cnt++] = line;
br.close();
for (int i = 0; i < cnt; ++i) {
for (int j = 0; j < a[i].length(); ++j) {
solve(i, j, 0, 1);// 右
solve(i, j, 1, 0);// 下
solve(i, j, 1, 1);// 右下
}
}
System.out.println(ans);//16520
}
}
试题 C 蛇形填数
问题描述
如下图所示,小明用从 \(1\) 开始的正整数“蛇形”填充无限大的矩阵。
1 2 6 7 15 16 28 ...
3 5 8 14 17 27 ...
4 9 13 18 26 ...
10 12 19 25 ...
11 20 24 ...
21 23 ...
22...
容易看出矩阵第二行第二列中的数是 \(5\)。请你计算矩阵中第 \(20\) 行第 \(20\) 列的数是多少?
答案:\(761\)
思路
模拟
以一个蛇形来回为一次循环模拟填数操作
//761
public class Main {
//(2,2)->3
//(3,3)->5
//(4,4)->7
//(n,n)->2*n-1
//(20,20)->2*20-1=39
static final int n = 20;
static final int N = 2 * n - 1;
public static void main(String[] args) {
int x = 1, y = 2, now = 2;
while (x <= N && y <= N) {
//↙
while (y > 1) {
x += 1;
y -= 1;
++now;
if (x == n && y == n) {
System.out.println(now);
return;
}
}
//↓
x += 1;
++now;
//↗
while (x > 1) {
x -= 1;
y += 1;
++now;
if (x == n && y == n) {
System.out.println(now);
return;
}
}
//→
y += 1;
++now;
}
}
}
找规律
\((20,20)\) 位于主对角线位置,观察主对角线的值,相邻元素的差值是以公差 \(d=4\) 的等差数列首项 \(a_1=0\) 开始递增的
等差数列求和公式: \(S_n=\dfrac{(a_1+a_n)\times n}{2}=n\cdot a_1+\dfrac{n\cdot(n-1)}{2}\cdot d\)
则 \((k,k)\) 的值为 :
因此,\((20,20)\) 的值为:\(Fun(20)=1+\dfrac{20\times(20-1)}{2}\times 4=761\)
试题 D 七段码
问题描述
小蓝要用七段码数码管来表示一种特殊的文字。
上图给出了七段码数码管的一个图示,数码管中一共有 \(7\) 段可以发光的二 极管,分别标记为 \(a, b, c, d, e, f, g\)。
小蓝要选择一部分二极管(至少要有一个)发光来表达字符。在设计字符 的表达时,要求所有发光的二极管是连成一片的。
- 例如:\(b\) 发光,其他二极管不发光可以用来表达一种字符。
- 例如:\(c\) 发光,其他二极管不发光可以用来表达一种字符。这种方案与上一行的方案可以用来表示不同的字符,尽管看上去比较相似。
- 例如:\(a, b, c, d, e\) 发光,$f, g $ 不发光可以用来表达一种字符。
- 例如:\(b, f\) 发光,其他二极管不发光则不能用来表达一种字符,因为发光 的二极管没有连成一片。
请问,小蓝可以用七段码数码管表达多少种不同的字符?
答案:\(80\)
思路
注意不连通的图不能用来表达字符,否则结果直接为 \(2^7-1\)
前置知识
以下采用并查集判断连通图
并查集代表点的写法
import java.util.Arrays;
public class Main {
static final int n = 7;
//father[i]表示i的父节点
static int[] father;
// 并查集初始化每个点为一个集合
static void initDSU() {
// n条边对应n-1个点
father = new int[n - 1];
for (int i = 0; i < father.length; i++) father[i] = i;
}
// 寻找根节点并进行路径压缩
static int find(int x) {
if (x == father[x]) return x;
return father[x] = find(father[x]);
}
//合并两个集合
static boolean merge(int x, int y) {
x = find(x);
y = find(y);
//属于同一个集合
if (x == y) return false;
//将y所在集合加入x
father[y] = x;
return true;
}
// 无向边
static class edge {
int x, y;
public edge(int x, int y) {
this.x = x;
this.y = y;
}
}
static edge[] edges;
// 初始化边集
static void initAdj() {
edges = new edge[n];
edges[0] = new edge(0, 1);
edges[1] = new edge(0, 2);
edges[2] = new edge(2, 3);
edges[3] = new edge(1, 3);
edges[4] = new edge(2, 4);
edges[5] = new edge(3, 5);
edges[6] = new edge(4, 5);
}
//是否选择该边
static boolean[] bookEdge;
//图中的点
static boolean[] bookVertex;
// 判断是否为联通图
static boolean check() {
initDSU();
Arrays.fill(bookVertex, false);
// 选择的边数
for (int i = 0; i < n; ++i) {
if (bookEdge[i]) {
//选点
bookVertex[edges[i].x] = true;
bookVertex[edges[i].y] = true;
//合并该边的两个端点所在集合
merge(edges[i].x, edges[i].y);
}
}
int cnt = 0;
for (int i = 0; i < father.length; ++i) {
//如果该点在图中,判断集合个数
if (bookVertex[i] && father[i] == i) ++cnt;
}
return cnt == 1;
}
static int ans = 0;
//dfs以组合形式选边
//now表示当前边
static void dfs(int now) {
//所有边都选择完了
if (now == n) {
if (check()) ++ans;
return;
}
//选该边
bookEdge[now] = true;
dfs(now + 1);
//不选该边
bookEdge[now] = false;
dfs(now + 1);
}
public static void main(String[] args) {
initAdj();
bookEdge = new boolean[n];
bookVertex = new boolean[n - 1];
dfs(0);
System.out.println(ans);
}
}
将二极管看作点
public class Main {
static final int n = 7;
//father[i]表示i的父节点
static int[] father;
// 并查集初始化每个点为一个集合
static void initDSU(final int n) {
father = new int[n];
for (int i = 0; i < n; i++) father[i] = i;
}
// 寻找根节点并进行路径压缩
static int find(int x) {
if (x == father[x]) return x;
return father[x] = find(father[x]);
}
//合并两个集合
static boolean merge(int x, int y) {
x = find(x);
y = find(y);
//属于同一个集合
if (x == y) return false;
//将y所在集合加入x
father[y] = x;
return true;
}
// 无向边
static class edge {
int x, y;
public edge(int x, int y) {
this.x = x;
this.y = y;
}
}
static edge[] edges;
// 初始化边集
// 0~n-1对应a~g二极管
// 每条边代表两个二极管相连
static void initAdj() {
edges = new edge[10];
edges[0] = new edge(0, 1);
edges[1] = new edge(0, 2);
edges[2] = new edge(1, 3);
edges[3] = new edge(2, 3);
edges[4] = new edge(1, 4);
edges[5] = new edge(2, 6);
edges[6] = new edge(3, 4);
edges[7] = new edge(3, 6);
edges[8] = new edge(4, 5);
edges[9] = new edge(5, 6);
}
//判断二极管x是否被选择
static boolean check(int num, int x) {
return (num >> x & 1) == 1;
}
static boolean solve(int num) {
initDSU(n);
for (edge e : edges) {
//如果相邻二极管被选择,则合并集合
if (check(num, e.x) && check(num, e.y)) {
merge(e.x, e.y);
}
}
//图中集合个数
int cnt = 0;
for (int i = 0; i < n; ++i) {
if (check(num, i) && father[i] == i) ++cnt;
}
return cnt == 1;
}
public static void main(String[] args) {
initAdj();
int ans = 0;
//二进制枚举组合
for (int i = (1 << n) - 1; i > 0; --i) {
if (solve(i)) ++ans;
}
System.out.println(ans);
}
}
试题 E 排序
问题描述
小蓝最近学习了一些排序算法,其中冒泡排序让他印象深刻。
在冒泡排序中,每次只能交换相邻的两个元素。
小蓝发现,如果对一个字符串中的字符排序,只允许交换相邻的两个字符,则在所有可能的排序方案中,冒泡排序的总交换次数是最少的。
例如,对于字符串 lan
排序,只需要 \(1\) 次交换。对于字符串 qiao
排序,总共需要 \(4\) 次交换。
小蓝找到了很多字符串试图 排序,他恰巧碰到一个字符串,需要 \(100\) 次交换,可是他忘了吧这个字符串记下来,现在找不到了。
请帮助小蓝找一个只包含小写英文字母且没有字母重复出现的字符串,对该串的字符排序,正好需要 100 次交换。如果可能找到多个,请告诉小蓝最短的那个。如果最短的仍然有多个,请告诉小蓝字典序最小的那个。请注意字符串中可以包含相同的字符。
答案:jonmlkihgfedcba
思路
前置知识:
- 冒泡排序的交换次数等于逆序对的个数
- 求逆序对
- 冒泡双重循环模拟(\(O(n^2)\))
- 树状数组(\(O(nlogn)\))
- 归并排序(\(O(nlogn)\))
- 全排列问题
最坏情况(完全逆序)下,交换次数(逆序对个数)为 \(\dfrac{n\cdot(n-1)}{2}\)
假设数组的长度为 \(n\),交换次数最大,即每个数字都需要与相邻的数字交换
从左往右数串的第一位开始冒泡,第一位交换的次数为 \(n-1\),第二位交换的次数为 \(n-2\),…,最后一位交换的次数为 \(n-n=0\)
交换的总次数为:\((n-1)+(n-2)+\cdots+0=\dfrac{n\cdot(n-1)}{2}\)
定义交换次数为 \(fun(n)\),则 \(fun(14)=91,fun(15)=105\),因此 \(n=15\)
即需要长度为 \(15\) 的字符串,确定字符串为onmlkjihgfedcba
这时可以选择编程求从小到大的排列字符串对应的逆序数来解决
import java.io.IOException;
import java.util.Arrays;
public class Main {
static void swap(char[] a, int x, int y) {
char t = a[x];
a[x] = a[y];
a[y] = t;
}
// 冒泡交换次序
// static int getNumber(char[] a, int l, int r) {
// int cnt = 0;
// for (int i = 1; i < r - l + 1; ++i) {
// for (int j = l; j <= r - i; ++j) {
// if (a[j] > a[j + 1]) {
// swap(a, j, j + 1);
// ++cnt;
// }
// }
// }
// return cnt;
// }
// 归并排序求逆序对
public static long mergeSort(char[] a, int left, int right, char[] temp) {
if (left >= right) return 0;
int mid = left + right >> 1;
long ret = mergeSort(a, left, mid, temp) + mergeSort(a, mid + 1, right, temp);
int k = 0, l = left, r = mid + 1;
while (l <= mid && r <= right) {
if (a[l] <= a[r]) {
temp[k++] = a[l++];
} else {
temp[k++] = a[r++];
ret += mid - l + 1;
}
}
while (l <= mid) temp[k++] = a[l++];
while (r <= right) temp[k++] = a[r++];
for (int i = left, j = 0; i <= right; ++i, ++j) a[i] = temp[j];
return ret;
}
// 下一个排列
public static boolean nextPermutation(char[] arr, int l, int r) {
if (arr.length <= 1) return false;
int i = r - 1;
while (i >= l && arr[i] >= arr[i + 1]) --i;
//该数组非递增,即为最后一个排列
if (i == l - 1) {
for (; l < r; ++l, --r) swap(arr, l, r);
return false;
}
int k = r;
while (arr[i] >= arr[k]) --k;
swap(arr, k, i);
for (++i; i < r; ++i, --r) swap(arr, i, r);
return true;
}
public static void main(String[] args) throws IOException {
char[] a = "abcdefghijklmno".toCharArray();
do {
if (mergeSort(Arrays.copyOf(a, a.length), 0, a.length - 1) == 100) {
System.out.println(String.valueOf(a));
break;
}
} while (nextPermutation(a, 0, a.length - 1));
}
}
此时需要再减少 \(5\) 次交换次数
由于最后需要字典序最小的字符串,选择靠前的第 \(6\) 个字符移至第一位,使其少交换 \(5\) 次,字符串为jonmlkihgfedcba
试题 F 成绩分析
问题描述
小蓝给学生们组织了一场考试,卷面总分为 100 分,每个学生的得分都是 一个 0 到 100 的整数。
请计算这次考试的最高分、最低分和平均分。
【输入格式】
输入的第一行包含一个整数 n,表示考试人数。
接下来 n 行,每行包含一个 0 至 100 的整数,表示一个学生的得分。
【输出格式】
输出三行。
第一行包含一个整数,表示最高分。
第二行包含一个整数,表示最低分。
第三行包含一个实数,四舍五入保留正好两位小数,表示平均分。
【样例输入】
7
80
92
56
74
88
99
10
【样例输出】
99
10
71.29
Code
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
int max = Integer.MIN_VALUE, min = Integer.MAX_VALUE;
int sum = 0;
for (int i = 0; i < n; ++i) {
int t = sc.nextInt();
sum += t;
max = Math.max(t, max);
min = Math.min(t, min);
}
System.out.println(max);
System.out.println(min);
System.out.printf("%.2f", (double) sum / n);
}
}
试题 G 单词分析
问题描述
小蓝正在学习一门神奇的语言,这门语言中的单词都是由小写英文字母组成,有些单词很长,远远超过正常英文单词的长度。小蓝学了很长时间也记不 住一些单词,他准备不再完全记忆这些单词,而是根据单词中哪个字母出现得 最多来分辨单词。
现在,请你帮助小蓝,给了一个单词后,帮助他找到出现最多的字母和这个字母出现的次数。
【输入格式】
输入一行包含一个单词,单词只由小写英文字母组成。
【输出格式】
输出两行,第一行包含一个英文字母,表示单词中出现得最多的字母是哪
个。如果有多个字母出现的次数相等,输出字典序最小的那个。
第二行包含一个整数,表示出现得最多的那个字母在单词中出现的次数。
【样例输入】
lanqiao
【样例输出】
a
2
【样例输入】
longlonglongistoolong
【样例输出】
o
6
Code
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
String line = sc.nextLine();
int[] num = new int[26];
for (int i = 0; i < line.length(); ++i) {
++num[line.charAt(i) - 'a'];
}
int ans = 0;
char ch = 'b';
for (int i = 0; i < 26; ++i) {
if (num[i] > ans) {
ans = num[i];
ch = (char) ('a' + i);
}
}
System.out.println(ch);
System.out.println(ans);
}
}
试题 H 数字三角形
问题描述
上图给出了一个数字三角形。从三角形的顶部到底部有很多条不同的路径。对于每条路径,把路径上面的数加起来可以得到一个和,你的任务就是找到最大的和。
路径上的每一步只能从一个数走到下一层和它最近的左边的那个数或者右边的那个数。此外,向左下走的次数与向右下走的次数相差不能超过 1。
【输入格式】
输入的第一行包含一个整数 N (1 < N ≤ 100),表示三角形的行数。下面的 N 行给出数字三角形。数字三角形上的数都是 0 至 100 之间的整数。
【输出格式】
输出一个整数,表示答案。
【样例输入】
5
7
3 8
8 1 0
2 7 4 4
4 5 2 6 5
【样例输出】
27
思路
数字金字塔dp
照常规思路 \(dp[i][j]\) 表示从 \((1,1)\) 到第 \((i,j)\) 的最大的和
由于向左下走的次数与向右下走的次数相差不能超过 \(1\),因此最后只会是最后一行的中位数
-
当行数为偶数时,有两个位置 \((n,\lfloor\dfrac{n}{2}\rfloor)\) 和 \((n,\lfloor\dfrac{n+1}{2}\rfloor)\)
-
当行数为奇数时,有一个位置 \((n,\dfrac{n}{2})\)
选择最大的一个
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.StreamTokenizer;
public class Main {
public static void main(String[] args) throws Exception {
StreamTokenizer in = new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in)));
in.nextToken();
int n = (int) in.nval;
int[][] dp = new int[n][n];
for (int i = 0; i < n; ++i) {
for (int j = 0; j <= i; ++j) {
in.nextToken();
dp[i][j] = (int) in.nval;
}
}
for (int i = 1; i < n; ++i) {
dp[i][0] += dp[i - 1][0];
for (int j = 1; j < i; ++j) dp[i][j] += Math.max(dp[i - 1][j - 1], dp[i - 1][j]);
dp[i][i] += dp[i - 1][i - 1];
}
if (n % 2 == 0) System.out.println(Math.max(dp[n - 1][(n - 1) / 2], dp[n - 1][(n - 1) / 2 + 1]));
else System.out.println(dp[n - 1][(n - 1) / 2]);
}
}
试题 I 子串分值和
问题描述
对于一个字符串 \(S\),我们定义 \(S\) 的分值 \(f (S )\) 为 \(S\) 中出现的不同的字符个数。例如 \(f (aba)=2,f(abc)=3,f (aaa)=1\)。
现在给定一个字符串 \(S[0..n − 1]\)(长度为 \(n\)),请你计算对于所有 \(S\) 的非空子串 \(S [i.. j](0 ≤ i ≤ j < n)\), \(f (S [i.. j])\) 的和是多少。
【输入格式】
输入一行包含一个由小写字母组成的字符串 \(S\)。
【输出格式】
输出一个整数表示答案。
【样例输入】
ababc
【样例输出】
28
【样例说明】
子串 f值
a 1
ab 2
aba 2
abab 2
ababc 3
b 1
ba 2
bab 2
babc 3
a 1
ab 2
abc 3
b 1
bc 2
c 1
【评测用例规模与约定】
对于 20% 的评测用例,1 ≤ n ≤ 10;
对于 40% 的评测用例,1 ≤ n ≤ 100;
对于 50% 的评测用例,1 ≤ n ≤ 1000;
对于 60% 的评测用例,1 ≤ n ≤ 10000;
对于所有评测用例,1 ≤ n ≤ 100000。
思路
还没写