2021年第十二届蓝桥杯国赛JAVA B组个人题解

等成绩太无聊了,不如来写个题解,纪念一下大学参加的最后一个比赛,以下是个人题解,不代表官方答案

试题 A: 整数范围

【问题描述】
用 8 位二进制(一个字节)来表示一个非负整数,表示的最小值是 0,则
一般能表示的最大值是多少?

:255

试题 B: 纯质数

【问题描述】
如果一个正整数只有 1 和它本身两个约数,则称为一个质数(又称素数)。
前几个质数是:2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, · · · 。
如果一个质数的所有十进制数位都是质数,我们称它为纯质数。例如:2,
3, 5, 7, 23, 37 都是纯质数,而 11, 13, 17, 19, 29, 31 不是纯质数。当然 1, 4, 35
也不是纯质数。
请问,在 1 到 20210605 中,有多少个纯质数?

:1903
代码

public class Q02 {
	public static Set<Character> set = new HashSet<>();
	static {
		set.add('2');
		set.add('3');
		set.add('5');
		set.add('7');
	}
	public static void main(String[] args) {
		int sum = 0;
		out: for (int i = 1; i <= 20210605; i++) {
			String s = String.valueOf(i);
			char[] arr = s.toCharArray();
			for (int j = 0; j < arr.length; j++) {
				if (!set.contains(arr[j]))	continue out;
			}
			if (isZhi(i))	sum++;
		}
		System.out.println(sum);
	}
	private static boolean isZhi(int value) {
		for (int i = 2; i < (value / 2) + 1; i++) {
			if (value % i == 0)	return false;
		}
		return true;
	}
}

试题 C: 完全日期

【问题描述】
如果一个日期中年月日的各位数字之和是完全平方数,则称为一个完全日
期。
例如:2021 年 6 月 5 日的各位数字之和为 2 + 0 + 2 + 1 + 6 + 5 = 16,而
16 是一个完全平方数,它是 4 的平方。所以 2021 年 6 月 5 日是一个完全日期。
例如:2021 年 6 月 23 日的各位数字之和为 2 + 0 + 2 + 1 + 6 + 2 + 3 = 16,
是一个完全平方数。所以 2021 年 6 月 23 日也是一个完全日期。
请问,从 2001 年 1 月 1 日到 2021 年 12 月 31 日中,一共有多少个完全日
期?

:977
代码

public class Q03 {
	public static Set<Integer> set = new HashSet<>();
	static {
		set.add(1);
		set.add(4);
		set.add(9);
		set.add(16);
		set.add(25);
		set.add(36);
		set.add(49);
		set.add(64);
		set.add(81);
	}
	public static void main(String[] args) {
		LocalDate start = LocalDate.of(2001, 1, 1);
		LocalDate temp = start;
		int sum = 0;
		while (true) {
			char[] year = String.valueOf(temp.getYear()).toCharArray();
			char[] month = String.valueOf(temp.getMonthValue()).toCharArray();
			char[] day = String.valueOf(temp.getDayOfMonth()).toCharArray();

			System.out.println(temp.getYear() + ":" + temp.getMonthValue() + ":" + temp.getDayOfMonth());
			
			int value = 0;
			for (int i = 0; i < year.length; i++) value += (year[i] - '0');
			for (int i = 0; i < month.length; i++) value += (month[i] - '0');
			for (int i = 0; i < day.length; i++) value += (day[i] - '0');
			
			if (set.contains(value))	sum++;
			if (temp.getYear() == 2021 && temp.getMonthValue() == 12 && temp.getDayOfMonth() == 31)	break;
			temp = temp.plusDays(1);
		}
		System.out.println(sum);
	}
}

试题 D: 最小权值

【问题描述】
对于一棵有根二叉树 T,小蓝定义这棵树中结点的权值 W(T) 如下:
空子树的权值为 0。
如果一个结点 v 有左子树 L, 右子树 R,分别有 C(L) 和 C® 个结点,则
W(v) = 1 + 2W(L) + 3W® + (C(L))
2 C®。
树的权值定义为树的根结点的权值。
小蓝想知道,对于一棵有 2021 个结点的二叉树,树的权值最小可能是多
少?
:2653631372
代码

public class Q04 {
	public static void main(String[] args) {
		long[] dp = new long[2022];

		Arrays.fill(dp, -1);
		dp[0] = 0;
		dp[1] = 1;

		for (int i = 2; i < dp.length; i++) {
			long min = Long.MAX_VALUE;
			for (int j = 0; j < i; j++) {
				int leftNum = j;
				int rightNum = i - j - 1;
				long temp = 1 + 2 * dp[leftNum] + 3 * dp[rightNum] + leftNum * leftNum * rightNum;
				min = Math.min(min, temp);
			}
			dp[i] = min;
		}
		System.out.println(Arrays.toString(dp));
		System.out.println(dp[2021]);
	}
}

试题 E: 大写

【问题描述】
给定一个只包含大写字母和小写字母的字符串,请将其中所有的小写字母
转换成大写字母后将字符串输出。

代码

public class Q05 {
	public static void main(String[] args) {
		Scanner sc = new Scanner(System.in);
		String text = sc.next();
		System.out.println(text.toUpperCase());
		sc.close();
	}
}

试题 F: 123

【问题描述】
小蓝发现了一个有趣的数列,这个数列的前几项如下:
1, 1, 2, 1, 2, 3, 1, 2, 3, 4, …
小蓝发现,这个数列前 1 项是整数 1,接下来 2 项是整数 1 至 2,接下来
3 项是整数 1 至 3,接下来 4 项是整数 1 至 4,依次类推。
小蓝想知道,这个数列中,连续一段的和是多少。

:估计暴力解会超时,所以用了等差数列求和公式
代码

public class Q06 {
	public static void main(String[] args) {
		Scanner sc = new Scanner(System.in);
		int n = sc.nextInt();
		while (n-- > 0) {
			long left = sc.nextLong();
			long right = sc.nextLong();
			System.out.println(solution(left, right));
		}
		sc.close();
	}

	public static long solution(long left, long right) {
		long startLun = 1;
		while (true) {
			if (left <= num(startLun))	break;
			startLun++;
		}
		long endLun = startLun;
		while (true) {
			if (right <= num(endLun))	break;
			endLun++;
		}

		long sum = 0;
		if (startLun != endLun) {
			long start = left - num(startLun - 1);
			sum += ((start + startLun) * (startLun - start + 1) / 2);
			for (long i = startLun + 1; i < endLun; i++) sum += num(i);
			long end = right - num(endLun - 1);
			sum += num(end);
		} else {
			long lastLunNum = num(startLun - 1);
			sum = (left - lastLunNum + right - lastLunNum) * (right - left + 1) / 2;
		}
		return sum;
	}

	public static long num(long value) {
		if (value == 0)	return 0;
		return (value * value + value) / 2;
	}
}

试题 G: 和与乘积

【问题描述】
给定一个数列 A = (a1, a2, · · · , an),问有多少个区间 [L, R] 满足区间内元素
的乘积等于他们的和,即 aL · aL+1 · · · aR = aL + aL+1 + · · · + aR 。

:dp,但是感觉有可能会超时
代码

public class Q07 {
	public static final long MAX = 200000 * 200000;
	public static void main(String[] args) {
		Scanner sc = new Scanner(System.in);
		int n = sc.nextInt();
		int[] arr = new int[n];
		for (int i = 0; i < n; i++) {
			arr[i] = sc.nextInt();
		}
		System.out.println(solution(n, arr));
		sc.close();
	}

	private static long solution(int n, int[] arr) {
		long[] mult = new long[n];
		long[] add = new long[n];
		long sum = 0;
		for (int i = 0; i < n; i++) {
			sum++;
			for (int j = 0; j < i; j++) {
				if (mult[j] > MAX)	continue;
				mult[j] *= arr[i];
				add[j] += arr[i];
				if (mult[j] == add[j])	sum++;
			}
			mult[i] = arr[i];
			add[i] = arr[i];
		}
		return sum;
	}
}

试题 H: 巧克力

【问题描述】
小蓝很喜欢吃巧克力,他每天都要吃一块巧克力。
一天小蓝到超市想买一些巧克力。超市的货架上有很多种巧克力,每种巧
克力有自己的价格、数量和剩余的保质期天数,小蓝只吃没过保质期的巧克力,
请问小蓝最少花多少钱能买到让自己吃 x 天的巧克力。

:从最后一天到第一天,然后贪心,这次比赛用的是jdk1.8,在这之前用的是1.6,但是为了小心起见,写比较器的时候还是没有用Lambda来写
代码

public class Q08 {
	public static void main(String[] args) {
		Scanner sc = new Scanner(System.in);
		int need = sc.nextInt();
		int types = sc.nextInt();
		Food[] foods = new Food[types];
		for (int i = 0; i < types; i++) {
			foods[i] = new Food(i, sc.nextInt(), sc.nextInt(), sc.nextInt());
		}
		System.out.println(solution(need, foods));
		sc.close();
	}
	private static long solution(int need, Food[] foods) {
		TreeSet<Food> noReady = new TreeSet<Food>(new Comparator<Food>() {
			@Override
			public int compare(Food o1, Food o2) {
				if (o1.day != o2.day)
					return o2.day - o1.day;
				return o1.index - o2.index;
			}
		});
		TreeSet<Food> hasReady = new TreeSet<Food>(new Comparator<Food>() {
			@Override
			public int compare(Food o1, Food o2) {
				if (o1.price != o2.price)
					return o1.price - o2.price;
				return o1.index - o2.index;
			}
		});
		for (Food f : foods) {
			if (f.day >= need)	hasReady.add(f);
			else	noReady.add(f);
		}
		long cost = 0;
		while (need > 0) {
			if (hasReady.isEmpty())	return -1;
			Food hasReadyFood = hasReady.first();
			hasReadyFood.num--;
			if (hasReadyFood.num <= 0)	hasReady.remove(hasReadyFood);
			cost += hasReadyFood.price;
			need--;
			while (!noReady.isEmpty() && noReady.first().day >= need) {
				Food firstFood = noReady.first();
				noReady.remove(firstFood);
				hasReady.add(firstFood);
			}
		}
		return cost;
	}
	static class Food {
		int index;
		int price;
		int day;
		int num;
		public Food(int index, int price, int day, int num) {
			this.index = index;
			this.price = price;
			this.day = day;
			this.num = num;
		}
	}
}

试题 I: 翻转括号序列

【问题描述】
给定一个长度为 n 的括号序列,要求支持两种操作:

  1. 将 [Li, Ri] 区间内(序列中的第 Li 个字符到第 Ri 个字符)的括号全部翻转(左括号变成右括号,右括号变成左括号)。
  2. 求出以 Li 为左端点时,最长的合法括号序列对应的 Ri (即找出最大的Ri 使 [Li, Ri] 是一个合法括号序列)。

:没有好的思路,只能用直接按题目要求上了
代码

public class Q09 {
	public static void main(String[] args) {
		Scanner sc = new Scanner(System.in);
		int len = sc.nextInt();
		int times = sc.nextInt();
		sc.nextLine();
		boolean[] kuo = new boolean[len];
		char[] arr = sc.nextLine().toCharArray();
		for (int i = 0; i < arr.length; i++) {
			kuo[i] = (arr[i] == '(');
		}
		for (int i = 0; i < times; i++) {
			int type = sc.nextInt();
			if (type == 1)
				typeOne(kuo, sc.nextInt(), sc.nextInt());
			else
				System.out.println(typeTwo(kuo, sc.nextInt()));
		}
		sc.close();
	}
	public static void typeOne(boolean[] kuo, int left, int right) {
		for (int i = left - 1; i < right - 1; i++) {
			kuo[i] = !kuo[i];
		}
	}
	public static int typeTwo(boolean[] kuo, int left) {
		Stack<Boolean> stack = new Stack<Boolean>();
		int maxRight = 0;
		for (int i = left - 1; i < kuo.length; i++) {
			if (kuo[i]) {
				stack.add(kuo[i]);
			} else if (stack.isEmpty()) {
				return maxRight;
			} else {
				stack.pop();
				if (stack.isEmpty())	maxRight = i + 1;
			}
		}
		return maxRight;
	}
}

试题 J: 异或三角

【问题描述】
给定 T 个数 n1, n2, · · · , nT,对每个 ni 请求出有多少组 a, b, c 满足:

  1. 1 ≤ a, b, c ≤ ni;
  2. a ⊕ b ⊕ c = 0,其中 ⊕ 表示二进制按位异或;
  3. 长度为 a, b, c 的三条边能组成一个三角形。

:比赛做到这道题的时候去前面几道题看了一遍,估计都没什么可以改的了,然后这道题看了很久,但是最后还是没有想出来,用的暴力剪枝,看了一下测试用例,50%的用例是t=1,n<2^20,剪枝后在我的电脑跑这种用例大概两秒,希望跑题的机器能好点让我骗点分。
剪枝思路:因为三个数异或为0,所以三个数在每一位上的排列不可能为两个0一个1,其他的排列都有可能,然后看数的第一个位,因为要组成三角形,所以三个数的首位排列是两个1一个0。此外确定了两个数之后可以用异或算出第三个数,再用三角形检验一下是否符合,最后算出的结果乘上3的全排列,也就是6。剪枝后的复杂度小于O(n2)。
代码

public class Q10 {
	public static void main(String[] args) {
		Scanner sc = new Scanner(System.in);
		int t = sc.nextInt();
		for (int i = 0; i < t; i++) {
			System.out.println(solution(sc.nextInt()));
		}
		sc.close();
	}
	public static long solution(int value) {
		if (value < 6)	return 0;
		int start = 2;
		int end = (start << 1);
		long sum = 0;
		while (value > start) {
			if (value <= end) 	end = value + 1;
			for (int i = start; i < end; i++) {
				for (int j = i + 1; j < end; j++) {
					int three = 0 ^ (i ^ j);
					if (j < i + three)	sum++;
				}
			}
			start = end;
			end = start << 1;
		}
		return sum * 6;
	}
}

版权声明:本文为qq_41901420原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。