2021年 第12届 蓝桥杯 Java B组 省赛真题详解及小结【第2场省赛 2021.05.09】


  1. 2013年 第04届 蓝桥杯 Java B组 省赛真题详解及小结
  2. 2014年 第05届 蓝桥杯 Java B组 省赛真题详解及小结
  3. 2015年 第06届 蓝桥杯 Java B组 省赛真题详解及小结
  4. 2016年 第07届 蓝桥杯 Java B组 省赛真题详解及小结
  5. 2017年 第08届 蓝桥杯 Java B组 省赛真题详解及小结
  6. 2018年 第09届 蓝桥杯 Java B组 省赛真题详解及小结
  7. 2019年 第10届 蓝桥杯 Java B组 省赛真题详解及小结
  8. 2020年 第11届 蓝桥杯 第1次模拟赛真题详解及小结【Java版】(校内模拟)// 官方讲解视频
  9. 2020年 第11届 蓝桥杯 第2次模拟赛真题详解及小结【Java版】// 官方讲解视频
  10. 2020年 第11届 蓝桥杯 C/C++ B组 省赛真题详解及小结【第1场省赛 2020.07.05】【Java版】
  11. 2020年 第11届 蓝桥杯 Java B组 省赛真题详解及小结【第1场省赛 2020.07.05】
  12. 2020年 第11届 蓝桥杯 Java B组 省赛真题详解及小结【第2场省赛 2020.10.17】
  13. 2020年 第11届 蓝桥杯 Java C组 省赛真题详解及小结【第1场省赛 2020.07.05】
  14. 2021年 第12届 蓝桥杯 Java B组 省赛真题详解及小结【第1场省赛 2021.04.18】
  15. 2021年 第12届 蓝桥杯 Java B组 省赛真题详解及小结【第2场省赛 2021.05.09】
  16. 2022年 第13届 蓝桥杯 Java B组 省赛真题详解及小结【第1场省赛 2022.04.09】

  1. 2015年 第06届 蓝桥杯 Java B组 决赛真题详解及小结
  2. 2016年 第07届 蓝桥杯 Java B组 决赛真题详解及小结
  3. 2017年 第08届 蓝桥杯 Java B组 决赛真题详解及小结
  4. 2018年 第09届 蓝桥杯 Java B组 决赛真题详解及小结
  5. 2019年 第10届 蓝桥杯 Java B组 决赛真题详解及小结
  6. 2020年 第11届 蓝桥杯 Java B组 决赛真题详解及小结【2020.11.14】
  7. 2021年 第12届 蓝桥杯 Java B组 决赛真题详解及小结【2021.06.05】
  8. 2022年 第13届 蓝桥杯 Java B组 决赛真题详解及小结【2022.00.00】

目录

一、试题A:求余(本题总分:5 分)

01解法一:直接计算

二、试题B:双阶乘(本题总分:5 分)

02解法一

02解法二:简单枚举

三、试题C:格点(本题总分:10 分)

03解法一:简单枚举

03解法二:朴素枚举

03解法三:稍微优化

四、试题D:整数分解(本题总分:10 分)

04解法一:排列组合

04解法二:组合公式

04解法三:动态规划

五、试题E:城邦(本题总分:15 分)

05解法一:最小生成树-Kruskal

05解法二:最小生成树-Prim

六、试题F:特殊年份(时间限制: 1.0s 内存限制: 512.0MB 本题总分:15 分)

06解法一:朴素解法

06解法二:BCD码

06解法三:取位判断

七、试题G:小平方(时间限制: 1.0s 内存限制: 512.0MB 本题总分:20 分)

07解法一:朴素解法

07解法二:简单枚举

八、试题H:完全平方数(时间限制: 2.0s 内存限制: 512.0MB 本题总分:20 分)

08解法一:简单数学

08解法二:朴素解法

九、试题I:负载均衡(时间限制: 2.0s 内存限制: 512.0MB 本题总分:25 分)

09解法一:优先队列模拟

09解法二:暴力求解

十、试题J:国际象棋(时间限制: 3.0s 内存限制: 512.0MB 本题总分:25 分)

10解法一:状压DP

10解法二

小结


参考博客:

  1. 第十二届蓝桥杯 2021年省赛真题 (Java 大学B组) 第二场_NOW I GOT ONE WAY-CSDN博客
  2. 第十二届蓝桥杯省赛第二场 Java B组真题个人题解_bughunter-的博客-CSDN博客

仅供参考,欢迎指正!部分为个人想法和解题思路,如有错误或不足,欢迎指正。

 

一、试题A:求余(本题总分:5 分)

本题总分:5 分

【问题描述】

        在 C/C++/Java/Python 等语言中,使用 % 表示求余,请问 2021%20 的值是多少?

【答案提交】

        这是一道结果填空的题,你只需要算出结果后提交即可。本题的结果为一个整数,在提交答案时只填写这个整数,填写多余的内容将无法得分。

【答案】:1

01解法一:直接计算

package provincialGames_12_2021_2_JavaB;

public class A01_求余 { // 1
	public static void main(String[] args) {
		System.out.println(2021%20); // 1
	}
}

二、试题B:双阶乘(本题总分:5 分)

本题总分:5 分

【问题描述】

        一个正整数的双阶乘,表示不超过这个正整数且与它有相同奇偶性的所有正整数乘积。n 的双阶乘用 n!! 表示。

        例如:

        3!! = 3 × 1 = 3。

        8!! = 8 × 6 × 4 × 2 = 384。

        11!! = 11 × 9 × 7 × 5 × 3 × 1 = 10395。

        请问,2021!! 的最后 5 位(这里指十进制位)是多少?

        注意:2021!! = 2021 × 2019 × · · · × 5 × 3 × 1。

        提示:建议使用计算机编程解决问题。

【答案提交】

        这是一道结果填空的题,你只需要算出结果后提交即可。本题的结果为一个整数,在提交答案时只填写这个整数,填写多余的内容将无法得分。

【答案】:59375

02解法一

( a × b ) % p = ( a % p × b % p ) % p ,p ∈ Z

package provincialGames_12_2021_2_JavaB;

public class B02_双阶乘 { // 59375
	public static void main(String[] args) {
		new B02_双阶乘().run();
	}

	void run() {
		int n = 2021 + 2, ans = 1;
		while (n > 1)
			ans = (ans * (n -= 2)) % 100000;
		System.out.println(ans); // 59375
	}
}

02解法二:简单枚举

【解析】:简单的枚举即可,需要注意的是只要最后五位,所以要模100000,而且必须是每次累乘之后都要模,因为数字太大肯定会溢出的,到最后才取模的话,数字就已经是溢出之后的了,那么结果肯定有问题的。

package provincialGames_12_2021_2_JavaB;

import java.util.Scanner;

public class B02_双阶乘2 { // 59375
	public static void main(String[] args) {
		Scanner in = new Scanner(System.in);
		long ans = 1;
		for (int i = 3; i <= 2021; i += 2) {
			ans *= i;
			ans %= 100000;
		}
		System.out.println(ans); // 59375
		in.close();
	}
}

三、试题C:格点(本题总分:10 分)

本题总分:10 分

【问题描述】

        如果一个点 (x, y) 的两维坐标都是整数,即 x ∈ Z 且 y ∈ Z,则称这个点为一个格点。

        如果一个点 (x, y) 的两维坐标都是正数,即 x > 0 且 y > 0,则称这个点在第一象限。

        请问在第一象限的格点中,有多少个点 (x, y) 的两维坐标乘积不超过 2021,即 x · y ≤ 2021。

        提示:建议使用计算机编程解决问题。

【答案提交】

        这是一道结果填空的题,你只需要算出结果后提交即可。本题的结果为一个整数,在提交答案时只填写这个整数,填写多余的内容将无法得分。

【答案】:15698

03解法一:简单枚举

【解析】:枚举x和y,范围都是1到2021,判断x*y<=2021计数即可。

package provincialGames_12_2021_2_JavaB;

import java.util.Scanner;

public class C03_格点 { // 15698
	public static void main(String[] args) {
		Scanner in = new Scanner(System.in);
		int ans = 0;
		for (int x = 1; x <= 2021; x++) {
			for (int y = 1; y <= 2021; y++) {
				if (x * y <= 2021) {
					ans++;
				}
			}
		}
		System.out.println(ans); // 15698
		in.close();
	}
}

03解法二:朴素枚举

package provincialGames_12_2021_2_JavaB;

public class C03_格点2 { // 15698
	public static void main(String[] args) {
		new C03_格点2().run();
	}

	int n = 2021;

	void run() {
		int ans = 0;
		for (int x = 1; x <= n; x++)
			for (int y = 1; y <= n; y++)
				if (x * y <= n)
					ans++;
		System.out.println(ans); // 15698
	}
}

03解法三:稍微优化

【解析】:题目解读一下就是多少个正整数二元组的乘积等于 k,k∈[1,2021],我们可以用欧拉筛线性的分解出区间内每个数的质因数,用其质数计算出组合方案的种数。

package provincialGames_12_2021_2_JavaB;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class C03_格点3 { // 15698
	public static void main(String[] args) {
		new C03_格点3().run();
	}

	int n = 2021, ans = 0;

	void run() {
		Map<Integer, Integer>[] nums = new Map[n + 1];
		nums[1] = new HashMap() {
			{
				put(0, 0);
			}
		};
		List<Integer> primes = new ArrayList();
		for (int i = 2; i <= n; i++) {
			if (nums[i] == null) {
				(nums[i] = new HashMap()).put(i, 1);
				primes.add(i);
			}
			for (int p : primes) {
				if (i * p > n)
					break;
				nums[i * p] = new HashMap(nums[i]);
				nums[i * p].put(p, nums[i].getOrDefault(p, 0) + 1);
				if (i % p == 0)
					break;
			}
		}
		for (int i = 1; i <= n; i++) {
			int count = 1;
			for (int k : nums[i].values())
				count *= k + 1;
			ans += count;
		}
		System.out.println(ans); // 15698
	}
}

四、试题D:整数分解(本题总分:10 分)

本题总分:10 分

【问题描述】

        将 3 分解成两个正整数的和,有两种分解方法,分别是 3 = 1 + 2 和 3 = 2 + 1。注意顺序不同算不同的方法。

        将 5 分解成三个正整数的和,有 6 种分解方法,它们是 1+1+3 = 1+2+2 = 1 + 3 + 1 = 2 + 1 + 2 = 2 + 2 + 1 = 3 + 1 + 1。

        请问,将 2021 分解成五个正整数的和,有多少种分解方法?

【答案提交】

        这是一道结果填空的题,你只需要算出结果后提交即可。本题的结果为一个整数,在提交答案时只填写这个整数,填写多余的内容将无法得分。

【答案】:691677274345

04解法一:排列组合

【解析】:暴力破解的话,2021的五次方几个小时都运行不出结果,一种可行的办法是使用数学中排列组合公式算出结果。数学方法,排列组合。

2020×2019×2018×2017÷4÷3÷2

package provincialGames_12_2021_2_JavaB;

public class D04_整数分解 { // 691677274345
	public static void main(String[] args) {
		new D04_整数分解().run();
	}

	int n = 2021;

	void run() {
		long ans = 0;
		for (int k = 1, a, b; k <= n - 2 - 2; k++) {
			for (a = 2, b = n - 2 - k; b >= 2; a++, b--)
				ans += (a - 1) * (b - 1);
		}
		System.out.println(ans); // 691677274345
	}
}

04解法二:组合公式

package provincialGames_12_2021_2_JavaB;

public class D04_整数分解2 { // 691677274345
	public static void main(String[] args) {
		new D04_整数分解2().run();
	}

	long n = 2021;

	void run() {
		System.out.println((n - 1) * (n - 2) * (n - 3) * (n - 4) / 4 / 3 / 2);
	}
}

04解法三:动态规划

package provincialGames_12_2021_2_JavaB;

import java.util.Arrays;

public class D04_整数分解3 { // 691677274345
	public static void main(String[] args) {
		new D04_整数分解3().run();
	}

	int n = 2021, k = 5;

	void run() {
		long[][] dp = new long[k + 1][n + 1];
		Arrays.fill(dp[1], 1);
		for (int i = 2; i <= k; i++)
			for (int j = i; j <= n; j++)
				dp[i][j] = dp[i - 1][j - 1] + dp[i][j - 1];
		System.out.println(dp[k][n]); // 691677274345
	}
}

五、试题E:城邦(本题总分:15 分)

本题总分:15 分

【问题描述】

        小蓝国是一个水上王国,有 2021 个城邦,依次编号 1 到 2021。在任意两个城邦之间,都有一座桥直接连接。

        为了庆祝小蓝国的传统节日,小蓝国政府准备将一部分桥装饰起来。

        对于编号为 a 和 b 的两个城邦,它们之间的桥如果要装饰起来,需要的费用如下计算:找到 a 和 b 在十进制下所有不同的数位,将数位上的数字求和。

        例如,编号为 2021 和 922 两个城邦之间,千位、百位和个位都不同,将这些数位上的数字加起来是 (2 + 0 + 1) + (0 + 9 + 2) = 14。注意 922 没有千位,千位看成 0。

        为了节约开支,小蓝国政府准备只装饰 2020 座桥,并且要保证从任意一个城邦到任意另一个城邦之间可以完全只通过装饰的桥到达。

        请问,小蓝国政府至少要花多少费用才能完成装饰。

        提示:建议使用计算机编程解决问题。

【答案提交】

        这是一道结果填空的题,你只需要算出结果后提交即可。本题的结果为一个整数,在提交答案时只填写这个整数,填写多余的内容将无法得分。

【答案】:4046

05解法一:最小生成树-Kruskal

package provincialGames_12_2021_2_JavaB;

import java.util.ArrayList;
import java.util.List;

public class E05_城邦 {
	public static void main(String[] args) {
		new E05_城邦().run();
	}

	int n = 2021;

	int[] link = new int[n + 1];

	void run() {
		List<Edge> edges = new ArrayList();
		for (int v = 1; v < n; v++) {
			link[v] = v;
			for (int w = v + 1; w <= n; w++)
				edges.add(new Edge(v, w, calc(v, w)));
		}
		edges.sort((e1, e2) -> (e1.weight - e2.weight));
		int ans = 0, k = 1;
		for (Edge edge : edges) {
			int v = find(edge.v);
			int w = find(edge.w);
			if (v != w) {
				link[v] = w;
				ans += edge.weight;
				if (++k == n)
					break;
			}
		}
		System.out.println(ans);
	}

	int find(int k) {
		return k == link[k] ? k : (link[k] = find(link[k]));
	}

	int calc(int v, int w) {
		int res = 0;
		for (int i = 0; i < 4; i++) {
			if (v % 10 != w % 10)
				res += v % 10 + w % 10;
			v /= 10;
			w /= 10;
		}
		return res;
	}

	class Edge {
		int v, w, weight;

		Edge(int v, int w, int weight) {
			this.v = v;
			this.w = w;
			this.weight = weight;
		}
	}
}

05解法二:最小生成树-Prim

package provincialGames_12_2021_2_JavaB;

import java.util.Arrays;

public class E05_城邦2 {
	public static void main(String[] args) {
		new E05_城邦2().run();
	}

	int n = 2021;

	void run() {
		boolean[] marked = new boolean[n + 1];
		int[] dist = new int[n + 1];
		Arrays.fill(dist, 0x7FFFFFFF);
		int ans = 0;
		dist[1] = 0;
		for (int k = 0; k < n; k++) {
			int V = 0;
			for (int i = 1; i <= n; i++)
				if (!marked[i] && dist[i] < dist[V])
					V = i;
			marked[V] = true;
			ans += dist[V];
			for (int i = 1; i <= n; i++)
				if (!marked[i])
					dist[i] = min(dist[i], calc(i, V));
		}
		System.out.println(ans);
	}

	int min(int a, int b) {
		return a < b ? a : b;
	}

	int calc(int v, int w) {
		int res = 0;
		for (int i = 0; i < 4; i++) {
			if (v % 10 != w % 10)
				res += v % 10 + w % 10;
			v /= 10;
			w /= 10;
		}
		return res;
	}
}

六、试题F:特殊年份(时间限制: 1.0s 内存限制: 512.0MB 本题总分:15 分)

时间限制: 1.0s 内存限制: 512.0MB 本题总分:15 分

【问题描述】

        今年是 2021 年,2021 这个数字非常特殊,它的千位和十位相等,个位比百位大 1,我们称满足这样条件的年份为特殊年份。

        输入 5 个年份,请计算这里面有多少个特殊年份。

【输入格式】输入 5 行,每行一个 4 位十进制数(数值范围为 1000 至 9999),表示一个年份。

【输出格式】输出一个整数,表示输入的 5 个年份中有多少个特殊年份。

【样例输入】2019 2021 1920 2120 9899

【样例输出】2

【样例说明】2021 和 9899 是特殊年份,其它不是特殊年份。

06解法一:朴素解法

package provincialGames_12_2021_2_JavaB;

import java.util.Scanner;

public class F06_特殊年份 {
	public static void main(String[] args) {
		new F06_特殊年份().run();
	}

	void run() {
		Scanner in = new Scanner(System.in);
		int ans = 0, k = 5;
		while (k-- > 0)
			if (check(in.nextInt()))
				ans++;
		System.out.println(ans);
	}

	boolean check(int year) {
		return (year / 1000 == (year % 100) / 10) && ((year % 10) - 1 == (year % 1000) / 100);
	}
}

06解法二:BCD码

package provincialGames_12_2021_2_JavaB;

import java.util.Scanner;

public class F06_特殊年份2 {
	public static void main(String[] args) {
		new F06_特殊年份2().run();
	}

	void run() {
		Scanner in = new Scanner(System.in);
		int ans = 0, k = 5;
		while (k-- > 0)
			if (check(in.nextInt(16)))
				ans++;
		System.out.println(ans);
	}

	boolean check(int year) {
		return (year >> 12 == ((year >> 4) & 0xf)) && ((year & 0xf) - 1 == ((year >> 8) & 0xf));
	}
}

06解法三:取位判断

【解析】:简单的取位+判断即可。

package provincialGames_12_2021_2_JavaB;

import java.util.Scanner;

public class F06_特殊年份3 {
	public static void main(String[] args) {
		Scanner in = new Scanner(System.in);
		int T = 5, ans = 0;
		while (T-- > 0) {
			int n = in.nextInt();
			int q = n / 1000 % 10;
			int b = n / 100 % 10;
			int s = n / 10 % 10;
			int g = n % 10;
			if (q == s && g - 1 == b) {
				ans++;
			}
		}
		System.out.println(ans);
		in.close();
	}
}

七、试题G:小平方(时间限制: 1.0s 内存限制: 512.0MB 本题总分:20 分)

时间限制: 1.0s 内存限制: 512.0MB 本题总分:20 分

【问题描述】

        小蓝发现,对于一个正整数 n 和一个小于 n 的正整数 v,将 v 平方后对 n 取余可能小于 n 的一半,也可能大于等于 n 的一半。

        请问,在 1 到 n − 1 中,有多少个数平方后除以 n 的余数小于 n 的一半。

        例如,当 n = 4 时,1, 2, 3 的平方除以 4 的余数都小于 4 的一半。

        又如,当 n = 5 时,1, 4 的平方除以 5 的余数都是 1,小于 5 的一半。而 2, 3 的平方除以 5 的余数都是 4,大于等于 5 的一半。

【输入格式】输入一行包含一个整数 n。

【输出格式】输出一个整数,表示满足条件的数的数量。

【样例输入】5

【样例输出】2

【评测用例规模与约定】 对于所有评测用例,1 ≤ n ≤ 10000。

07解法一:朴素解法

package provincialGames_12_2021_2_JavaB;

import java.util.Scanner;

public class G07_小平方 {
	public static void main(String[] args) {
		new G07_小平方().run();
	}

	void run() {
		int n = new Scanner(System.in).nextInt(), ans = 0;
		for (int i = 1; i < n; i++)
			if (i * i % n < n + 1 >> 1)
				ans++;
		System.out.println(ans);
	}
}

07解法二:简单枚举

【解析】:简单的枚举题,但是要尤其注意比较时的除法精度问题,if((i * i) % n < (float)n / 2),不要少了(float)强制类型转换。

package provincialGames_12_2021_2_JavaB;

import java.util.Scanner;

public class G07_小平方2 {
	public static void main(String[] args) {
		Scanner in = new Scanner(System.in);
		int n = in.nextInt();
		int ans = 0;
		for (int i = 1; i <= n - 1; i++) {
			if ((i * i) % n < (float) n / 2) { // 注意精度问题!!!
				ans++;
			}
		}
		System.out.println(ans);
		in.close();
	}
}

八、试题H:完全平方数(时间限制: 2.0s 内存限制: 512.0MB 本题总分:20 分)

时间限制: 2.0s 内存限制: 512.0MB 本题总分:20 分

【问题描述】

        一个整数 a 是一个完全平方数,是指它是某一个整数的平方,即存在一个整数 b,使得 a = b^2。

        给定一个正整数 n,请找到最小的正整数 x,使得它们的乘积是一个完全平方数。

【输入格式】输入一行包含一个正整数 n。

【输出格式】输出找到的最小的正整数 x。

【样例输入 1】12

【样例输出 1】3

【样例输入 2】15

【样例输出 2】15

【评测用例规模与约定】

        对于 30% 的评测用例,1 ≤ n ≤ 1000,答案不超过 1000。

        对于 60% 的评测用例,1 ≤ n ≤ 108,答案不超过 10^8。

        对于所有评测用例,1 ≤ n ≤ 1012,答案不超过 10^12。

08解法一:简单数学

一个完全平方数,它的质因子的指数总是个偶数。

因此,对给定的 n 分解质因数,将指数为奇数的质因子累乘起来,得到的积就是我们要找的x。

特别地,我们需要将枚举的范围缩小至\sqrt{n},以规避n为极大质数的情况。

package provincialGames_12_2021_2_JavaB;

import java.util.Scanner;

public class H08_完全平方数 {
	public static void main(String[] args) {
		new H08_完全平方数().run();
	}

	void run() {
		long n = new Scanner(System.in).nextLong();
		long ans = 1;
		for (long k = 2; k * k <= n; k++)
			if (n % k == 0) {
				int exp = 0;
				while (n % k == 0) {
					n /= k;
					exp++;
				}
				if (exp % 2 == 1)
					ans *= k;
			}
		System.out.println(n == 1 ? ans : n * ans);
	}
}

08解法二:朴素解法

判断x是不是完全平方数一个最朴素直接的办法是开根号之后强制转换为整型数据,砍掉后面的小数点,然后平方,看是否等于x。
这里要找出最小的i,使得i*n为一个完全平方数,那么从1枚举到n即可(最坏不会超过n了,自身的平方肯定是一个完全平方数)。
当然,该题的数据范围来看,这种解法会有个别数据样例超时,满分的解法是利用完全平方数的性质,分解质因数。
完全平方数的性质:除 1 外,一个完全平方数分解质因数后,各个质因数的指数都是偶数,如果一个数质分解后, 各个指数都为偶数, 那么它肯定是个平方数。完全平方数的所有因数的总个数是奇数个。因数个数为奇数的自然数一定是完全平方数。
这里我们先提供最朴素方法的代码。

package provincialGames_12_2021_2_JavaB;

import java.util.Scanner;

public class H08_完全平方数2 {
	public static void main(String[] args) {
		Scanner in = new Scanner(System.in);
		long n = in.nextLong();
		for (long i = 1; i <= n; i++) {
			if ((long) Math.sqrt(i * n) * (long) Math.sqrt(i * n) == i * n) {
				System.out.println(i);
				System.exit(0);
			}
		}
		in.close();
	}
}

九、试题I:负载均衡(时间限制: 2.0s 内存限制: 512.0MB 本题总分:25 分)

时间限制: 2.0s 内存限制: 512.0MB 本题总分:25 分

【问题描述】

        有 n 台计算机,第 i 台计算机的运算能力为 vi。

        有一系列的任务被指派到各个计算机上,第 i 个任务在 ai 时刻分配,指定计算机编号为 bi ,耗时为 ci 且算力消耗为 di 。如果此任务成功分配,将立刻开始运行,期间持续占用 bi 号计算机 di 的算力,持续 ci 秒。

        对于每次任务分配,如果计算机剩余的运算能力不足则输出 −1,并取消这次分配,否则输出分配完这个任务后这台计算机的剩余运算能力。

【输入格式】

        输入的第一行包含两个整数 n, m,分别表示计算机数目和要分配的任务数。

        第二行包含 n 个整数 v1, v2, · · · vn,分别表示每个计算机的运算能力。

        接下来 m 行每行 4 个整数 ai , bi , ci , di,意义如上所述。数据保证 ai 严格递 增,即 ai < ai+1。

【输出格式】输出 m 行,每行包含一个数,对应每次任务分配的结果。

【样例输入】

2 6
5 5
1 1 5 3
2 2 2 6
3 1 2 3
4 1 6 1
5 1 3 3
6 1 3 4

【样例输出】

2
-1
-1
1
-1
0

【样例说明】

        时刻 1,第 1 个任务被分配到第 1 台计算机,耗时为 5 ,这个任务时刻 6 会结束,占用计算机 1 的算力 3。
        时刻 2,第 2 个任务需要的算力不足,所以分配失败了。
        时刻 3,第 1 个计算机仍然正在计算第 1 个任务,剩余算力不足 3,所以失败。
        时刻 4,第 1 个计算机仍然正在计算第 1 个任务,但剩余算力足够,分配后剩余算力 1。
        时刻 5,第 1 个计算机仍然正在计算第 1, 4 个任务,剩余算力不足 4,失败。
        时刻 6,第 1 个计算机仍然正在计算第 4 个任务,剩余算力足够,且恰好用完。

【评测用例规模与约定】

        对于 20% 的评测用例,n, m ≤ 200。

        对于 40% 的评测用例,n, m ≤ 2000。

        对于所有评测用例,1 ≤ n, m ≤ 200000,1 ≤ ai , ci , di , vi ≤ 10^9,1 ≤ bi ≤ n。

09解法一:优先队列模拟

以为是个Big Implementation,构思完后发现代码量还蛮少的。
直接用优先队列来模拟每个计算机正在运行的任务,当当前计算机即将可能被分配任务时,清除队列中已经被执行完的任务,并归还算力即可。看到那种输入有各种各样的变量的题,基本都是模拟。

package provincialGames_12_2021_2_JavaB;

import java.io.*;
import java.util.*;

public class I09_负载均衡 {
	public static void main(String[] args) {
		new I09_负载均衡().run();
	}

	void run() {
		InputReader in = new InputReader(System.in);
		PrintWriter out = new PrintWriter(System.out);
		int n = in.readInt(), m = in.readInt();
		Queue<Item>[] cpt = new Queue[n + 1];
		int[] cptd = new int[n + 1];
		for (int i = 1; i <= n; i++) {
			cpt[i] = new PriorityQueue();
			cptd[i] = in.readInt();
		}
		while (m-- > 0) {
			int a = in.readInt();
			int b = in.readInt();
			int c = in.readInt();
			int d = in.readInt();
			while (cpt[b].size() > 0 && cpt[b].peek().c <= a)
				cptd[b] += cpt[b].poll().d;
			if (cptd[b] >= d) {
				cpt[b].offer(new Item(a + c, d));
				out.println(cptd[b] -= d);
			} else
				out.println("-1");
		}
		out.flush();
	}

	class Item implements Comparable<Item> {
		int c, d;

		Item(int c, int d) {
			this.c = c;
			this.d = d;
		}

		public int compareTo(Item item) {
			return this.c - item.c;
		}
	}

	class InputReader {
		BufferedReader reader;
		StringTokenizer token;

		InputReader(InputStream in) {
			this.reader = new BufferedReader(new InputStreamReader(in));
		}

		String read() {
			while (token == null || !token.hasMoreTokens()) {
				try {
					token = new StringTokenizer(reader.readLine());
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
			return token.nextToken();
		}

		int readInt() {
			return Integer.parseInt(read());
		}
	}
}

09解法二:暴力求解

首先搞懂题目意思,建议在纸上模拟一遍过程。

模拟,开辟一个二维数组T,T[i][j]存放的是i号计算机在时刻T还剩余多少算力。

但是,这个朴素的方法显然不会是拿到满分的最优解,要能够处理题目要求的最大数据,数组要开到200005 * 1000000005,先别说题目的内存限制了,这么大的数组根本不能开辟(爆内存)。

所以我的数组只开到了10005 * 10005,大概只能过40%的数据吧。(将暴力骗分进行到底)

据说正确解法应该是使用栈。

package provincialGames_12_2021_2_JavaB;

import java.util.*;

public class I09_负载均衡2 {
	public static int[][] T = new int[10005][10005]; // T[i][j]存储的是i号计算机在时刻j还剩余多少算力

	public static void main(String[] args) {
		Scanner in = new Scanner(System.in);
		int n = in.nextInt(); // 计算机台数
		int m = in.nextInt(); // 要分配的任务数
		// 输入每台计算机的算力
		for (int i = 1; i <= n; i++) {
			int cacl = in.nextInt();
			// 初始化存放各计算机当前剩余算力的数组T
			for (int j = 1; j <= 5000; j++) {
				T[i][j] = cacl;
			}
		}
		// 输入m次任务分配详情,并判断本次分配结果
		while (m-- > 0) {
			int ai = in.nextInt(); // 时刻ai
			int bi = in.nextInt(); // 分配给几号计算机
			int ci = in.nextInt(); // 任务占用耗时
			int di = in.nextInt(); // 任务消耗的算力
			if (T[bi][ai] < di) { // 算力不够,无法分配,取消该次分配
				System.out.println(-1);
				continue;
			}
			for (int j = ai; j < ai + ci; j++) { // 占用时长为ci,消耗算力di
				T[bi][j] -= di;
			}
			System.out.println(T[bi][ai]);
		}
		in.close();
	}
}

十、试题J:国际象棋(时间限制: 3.0s 内存限制: 512.0MB 本题总分:25 分)

时间限制: 3.0s 内存限制: 512.0MB 本题总分:25 分

【问题描述】

        众所周知,“八皇后” 问题是求解在国际象棋棋盘上摆放 8 个皇后,使得两两之间互不攻击的方案数。已经学习了很多算法的小蓝觉得 “八皇后” 问题太简单了,意犹未尽。作为一个国际象棋迷,他想研究在 N × M 的棋盘上,摆放 K 个马,使得两两之间互不攻击有多少种摆放方案。由于方案数可能很大,只需计算答案除以 1000000007 (即 109 + 7) 的余数。

        如下图所示,国际象棋中的马摆放在棋盘的方格内,走 “日” 字,位于 (x, y) 格的马(第 x 行第 y 列)可以攻击 (x + 1, y + 2)、(x + 1, y − 2)、(x − 1, y + 2)、(x − 1, y − 2)、(x + 2, y + 1)、(x + 2, y − 1)、(x − 2, y + 1) 和 (x − 2, y − 1) 共 8 个格子。

【输入格式】输入一行包含三个正整数 N, M, K,分别表示棋盘的行数、列数和马的个数。

【输出格式】输出一个整数,表示摆放的方案数除以 1000000007 (即 109 + 7) 的余数。

【样例输入】1 2 1

【样例输出】2

【样例输入】4 4 3

【样例输出】276

【样例输入】3 20 12

【样例输出】914051446

【评测用例规模与约定】

        对于 5% 的评测用例,K = 1;

        对于另外 10% 的评测用例,K = 2;

        对于另外 10% 的评测用例,N = 1;

        对于另外 20% 的评测用例,N, M ≤ 6,K ≤ 5;

        对于另外 25% 的评测用例,N ≤ 3,M ≤ 20,K ≤ 12;

        对于所有评测用例,1 ≤ N ≤ 6,1 ≤ M ≤ 100,1 ≤ K ≤ 20。

10解法一:状压DP

package provincialGames_12_2021_2_JavaB;

import java.util.Scanner;

public class J10_国际象棋 {
	public static void main(String[] args) {
		new J10_国际象棋().run();
	}

	final int mod = 1000000007;

	void run() {
		Scanner in = new Scanner(System.in);
		int N = 1 << in.nextInt(), M = in.nextInt(), K = in.nextInt();
		int[][][][] dp = new int[M + 1][N][N][K + 1];
		dp[0][0][0][0] = 1;
		for (int m = 1; m <= M; m++)
			for (int now = 0; now < N; now++)
				for (int pr1 = 0; pr1 < N; pr1++)
					if ((now << 2 & pr1) == 0 && (pr1 << 2 & now) == 0)
						for (int pr2 = 0; pr2 < N; pr2++)
							if ((now << 1 & pr2) == 0 && (pr2 << 1 & now) == 0)
								for (int k = bitCount(now), g = k; k <= K; k++)
									dp[m][now][pr1][k] = (dp[m][now][pr1][k] + dp[m - 1][pr1][pr2][k - g]) % mod;
		int ans = 0;
		for (int now = 0; now < N; now++)
			for (int pr1 = 0; pr1 < N; pr1++)
				if ((now << 2 & pr1) == 0 && (pr1 << 2 & now) == 0)
					ans = (ans + dp[M][now][pr1][K]) % mod;
		System.out.println(ans);
	}

	public static int bitCount(int n) {
		n = n - ((n >>> 1) & 0x55555555);
		n = (n & 0x33333333) + ((n >>> 2) & 0x33333333);
		n = (n + (n >>> 4)) & 0x0f0f0f0f;
		n = n + (n >>> 8);
		n = n + (n >>> 16);
		return n & 0x3f;
	}
}

10解法二

看到题目果断深度优先搜索+回溯。开辟一个二维数组记录哪些位置有棋子,再开辟一个二维数组记录哪些位置会被攻击。递归下探,每次下探到一个格子的时候,有放和不放两种决策,放的话要保证该位置不会被攻击,且该棋子攻击范围内也没有其他棋子(检查数组标记即可),做好标记,然后递归下探尝试下一个格子。直到成功放下K个棋子或者整个棋盘尝试完毕,递归返回时要进行回溯(即把标记取消)。

为节省代码量,开辟一个增量数组,存放可以攻击到的位置的坐标增量(地图、棋盘类深搜题基本都可以用这个小技巧),攻击位置的标记和取消可以专门封装成函数。

不过。。。。这个方法复杂度大约为O(2^(mn)),只能过部分的测试数据,稍大的数据便会超时。大佬思路:状压DP。

package provincialGames_12_2021_2_JavaB;

import java.util.Scanner;

public class J10_国际象棋2 {
	public static int n, m, K; // 在n行m列的棋盘上摆放k个马
	public static int[][] rec = new int[10][105]; // 记录哪些位置有棋子了
	public static int[][] attack = new int[10][105]; // 标记哪些位置会被攻击
	public static int[][] direction = new int[][] { // 位移增量数组(可以攻击到的位置)
			{ 1, 2 }, { 1, -2 }, { -1, 2 }, { -1, -2 }, { 2, 1 }, { 2, -1 }, { -2, 1 }, { -2, -1 } };
	public static int ans = 0;
	public static int mod = 1000000007;

	public static void dfs(int row, int col, int k) {
		if (k == K) { // 成功放下了K个棋子
			ans++;
			return;
		}
		if (row == n) { // 到达边界
			return;
		}
		if (attack[row][col] == 0 && check(row, col)) { // 该位置不受攻击且不会攻击到其他的棋子
			// 标记
			rec[row][col] = 1; // 标记该位置放了一个棋子
			attack(row, col); // 标记会被该棋子攻击到的位置
			// 下探,开始尝试下一个位置
			dfs(row + ((col + 1) / m), (col + 1) % m, k + 1);
			// 回溯
			rec[row][col] = 0;
			cancel(row, col);
		}
		// 该位置不放,开始尝试下一个位置
		dfs(row + ((col + 1) / m), (col + 1) % m, k); // 这样写可实现自动换行
	}

	public static void attack(int row, int col) { // 标记被攻击的位置
		for (int i = 0; i < 8; i++) {
			int nrow = row + direction[i][0];
			int ncol = col + direction[i][1];
			if (nrow >= 0 && nrow < n && ncol >= 0 && ncol < m) {
				attack[nrow][ncol] = 1;
			}
		}
	}

	public static void cancel(int row, int col) { // 用于回溯时取消被攻击位置的标记
		for (int i = 0; i < 8; i++) {
			int nrow = row + direction[i][0];
			int ncol = col + direction[i][1];
			if (nrow >= 0 && nrow < n && ncol >= 0 && ncol < m) {
				attack[nrow][ncol] = 0;
			}
		}
	}

	public static boolean check(int row, int col) { // 检查该位置上的棋子攻击范围内是否没有其他棋子
		for (int i = 0; i < 8; i++) {
			int nrow = row + direction[i][0];
			int ncol = col + direction[i][1];
			if (nrow >= 0 && nrow < n && ncol >= 0 && ncol < m && rec[nrow][ncol] == 1) {
				return false;
			}
		}
		return true;
	}

	public static void main(String[] args) {
		Scanner in = new Scanner(System.in);
		n = in.nextInt();
		m = in.nextInt();
		K = in.nextInt();
		dfs(0, 0, 0);
		System.out.println(ans);
		in.close();
	}
}

小结

好好学习~

  • 6
    点赞
  • 53
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值