2020年第十一届蓝桥杯javaB组国赛


以下均为个人想法和解题思路,如有错误或不足,欢迎指正。


试题 A: 美丽的 2

本题总分:5 分

  • 【问题描述】

小蓝特别喜欢 2,今年是公元 2020 年,他特别高兴。
他很好奇,在公元 1 年到公元 2020 年(包含)中,有多少个年份的数位中包含数字 2?

  • 【答案提交】

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

个人答案:563

个人代码:

public class _美丽的2 {
	public static void main(String[] args) {
		int res = 0;
		for (int i = 1; i <= 2020; ++i) {
			if (new String(i + "").indexOf("2") != -1) {
				++res;
			}
		}
		System.out.println(res);
	}
}

解题思路:第一题属于签到题,直接暴力就好了


试题 B: 扩散

本题总分:5 分

  • 【问题描述】

小蓝在一张无限大的特殊画布上作画。这张画布可以看成一个方格图,每个格子可以用一个二维的整数坐标示。
小蓝在画布上首先点了一下几个点:(0, 0), (2020, 11), (11, 14), (2000, 2000)。只有这几个格子上有黑色,其它位置都是白色的。
每过一分钟,黑色就会扩散一点。
具体的,如果一个格子里面是黑色,它就会扩散到上、下、左、右四个相邻的格子中,使得这四个格子也变成黑色(如果原来就是黑色,则还是色)。
请问,经过 2020 分钟后,画布上有多少个格子是黑色的。

  • 【答案提交】

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

个人答案:20312088

个人代码:


import java.util.LinkedList;
import java.util.Queue;

public class _扩散 {

	static boolean[][] vis = new boolean[10000][10000];

	public static void main(String[] args) {
		bfs();
		System.out.println(res);
	}

	static int res = 0;
	static int[][] dir = new int[][] { { 1, 0 }, { -1, 0 }, { 0, 1 }, { 0, -1 } };

	public static void bfs() {
		//初始化二维空间
		vis[0 + 4000][0 + 4000] = vis[2020 + 4000][11 + 4000] = vis[11 + 4000][14 + 4000] = vis[2000 + 4000][2000 + 4000] = true;
		
		Queue<Integer> que = new LinkedList<Integer>();
		que.add(4000 * 10000 + 4000);
		que.add(6020 * 10000 + 4011);
		que.add(4011 * 10000 + 4014);
		que.add(6000 * 10000 + 6000);
		res = 4;
		
		//一分钟一分钟地模拟扩散情况
		for (int i = 0; i < 2020; ++i) {
			int size = que.size();
			for (int j = 0; j < size; ++j) {
				int p = que.poll();
				int x = p / 10000, y = p % 10000;
				for (int k = 0; k < 4; ++k) {
					int nx = x + dir[k][0], ny = y + dir[k][1];
//					if(nx<0 || ny<0 || nx>=10000 || ny>=10000) continue;		//不需要这一步,因为vis数组的范围足够大,所以2020分钟内不会扩散到vis的边缘
					if (!vis[nx][ny]) {
						vis[nx][ny] = true;
						que.add(nx * 10000 + ny);
						++res;
					}
				}
			}
		}
	}
}

解题思路:创造一个足够大的二维空间用作判断将要通过的格子是否已经变黑,然后通过BFS一分钟一分钟地往下模拟


试题 C: 阶乘约数

本题总分:10 分

  • 【问题描述】

定义阶乘 n! = 1 × 2 × 3 × · · · × n。
请问 100! (100 的阶乘)有多少个约数。

  • 【答案提交】

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

个人答案:不会做,哈哈


试题 D: 本质上升序列

本题总分:10 分

  • 【问题描述】

小蓝特别喜欢单调递增的事物。
在一个字符串中,如果取出若干个字符,将这些字符按照在字符串中的顺序排列后是单调递增的,则成为这个字符串中的一个单调递增子序列。
例如,在字符串 lanqiao 中,如果取出字符 n 和 q,则 nq 组成一个单调递增子序列。类似的单调递增子序列还有 lnq、i、ano 等等。
小蓝发现,有些子序列虽然位置不同,但是字符序列是一样的,例如取第二个字符和最后一个字符可以取到 ao,取最后两个字符也可以取到 ao。小蓝认为他们并没有本质不同。
对于一个字符串,小蓝想知道,本质不同的递增子序列有多少个?
例如,对于字符串 lanqiao,本质不同的递增子序列有 21 个。
它们分别是 l、a、n、q、i、o、ln、an、lq、aq、nq、ai、lo、ao、no、io、lnq、anq、lno、ano、aio。
请问对于以下字符串(共 200 个小写英文字母,分四行显示):(如果你把以下文字复制到文本文件中,请务必检查复制的内容是否与文档中的一致。在试题目录下有一个文件 inc.txt,内容与下面的文本相同)
tocyjkdzcieoiodfpbgcncsrjbhmugdnojjddhllnofawllbhf
iadgdcdjstemphmnjihecoapdjjrprrqnhgccevdarufmliqij
gihhfgdcmxvicfauachlifhafpdccfseflcdgjncadfclvfmad
vrnaaahahndsikzssoywakgnfjjaihtniptwoulxbaeqkqhfwl
本质不同的递增子序列有多少个?

  • 【答案提交】

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

个人答案:3616159

个人代码:

import java.util.Arrays;

public class _本质上升序列 {
	public static void main(String[] args) {

		String str = new String("tocyjkdzcieoiodfpbgcncsrjbhmugdnojjddhllnofawllbhf"
							  + "iadgdcdjstemphmnjihecoapdjjrprrqnhgccevdarufmliqij"
				              + "gihhfgdcmxvicfauachlifhafpdccfseflcdgjncadfclvfmad"
				              + "vrnaaahahndsikzssoywakgnfjjaihtniptwoulxbaeqkqhfwl");

		char[] ch = str.toCharArray();
		//创建一维数组进行记录递增子序列个数,其中dp[i]表示第i个字符与前面的字符(前面指当前字符的左边)能够组合的递增子序列个数(如“lanqiao”和i=2,则dp[i]=3,即n、an、ln)
		int[] dp = new int[ch.length];
		//初始化dp数组,让所有元素的初始状态都为1,原因是其自身(单个字符)就属于一个递增子序列(单个字符有可能重复出现,所以在下面代码中如果发现重复,则要进行减一操作)
		Arrays.fill(dp, 1);

		int res = 0;
		
		//从左到右逐个遍历
		for (int i = 0; i < ch.length; ++i) {
			//从右到左与前面的字符逐个相比较,如果“当前字符”比“相比较的字符”大时,即“当前字符”与“相比较的字符”的所有递增子序列都能够组合成新的递增子序列;
			//如果“当前字符”与“相比较的字符”相同时,则可以停下不再往左比较了(原因是“相比较的字符”已经记录了所有 “当前字符”与“再往左边的字符”组合的所有递增子序列),
			//并且要进行减一操作(如“lanqiao”中, dp[1]和dp[5]的初始状态都记录了“a”这种递增子序列,所以要将dp[5]进行减一),
			for (int j = i - 1; j >= 0; --j) {
				if (ch[i] > ch[j]) {
					dp[i] += dp[j];
				} else if (ch[i] == ch[j]) {
					--dp[i];
					break;
				}
			}
			res += dp[i];
		}
		System.out.println(res);
	}
}

解题思路:动态规划题。具体请看代码注解。


试题 E: 玩具蛇

本题总分:15 分

  • 【问题描述】

小蓝有一条玩具蛇,一共有 16 节,上面标着数字 1 至 16。
每一节都是一个正方形的形状。
相邻的两节可以成直线或者成 90 度角。
小蓝还有一个 4 × 4 的方格盒子,用于存放玩具蛇,盒子的方格上依次标着字母 A 到 P 共 16 个字母。
小蓝可以折叠自己的玩具蛇放到盒子里面。他发现,有很多种方案可以将玩具蛇放进去。
下图给出了两种方案:
在这里插入图片描述
请帮小蓝计算一下,总共有多少种不同的方案。如果两个方案中,存在玩具蛇的某一节放在了盒子的不同格子里,则认为是不同的方案。

  • 【答案提交】

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

个人答案:552

个人代码:

public class _玩具蛇 {
	//创建一维数组记录每一节所在的位置,下标加一代表第几节,第1节所在的位置为(dp[0]/4, dp[0]%4)。
	static int[] r = new int[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 };

	public static void main(String[] args) {
		permutation(0);
		System.out.println(res);
	}

	static int res = 0;
	
	//全排列的模板
	public static void permutation(int x) {
		if (x == r.length) {
			++res;
			return;
		}
		for (int i = x; i < r.length; ++i) {
			swap(x, i);
			if (inspect(x)) {	//剪枝
				permutation(x + 1);
			}
			swap(x, i);
		}
	}

	public static void swap(int a, int b) {
		int t = r[a];
		r[a] = r[b];
		r[b] = t;
	}

	static int[][] dir = new int[][] { { 1, 0 }, { -1, 0 }, { 0, 1 }, { 0, -1 } };
	public static boolean inspect(int cur) {
		if (cur == 0)
			return true;
		//获取当前节的位置
		int x = r[cur] / 4, y = r[cur] % 4;
		//检查当前节的周围位置是否有等于前一节的位置
		for (int i = 0; i < 4; ++i) {
			int nx = x + dir[i][0], ny = y + dir[i][1];
			if (nx < 0 || ny < 0 || nx >= 4 | ny >= 4)
				continue;
			if (nx * 4 + ny == r[cur - 1])
				return true;
		}
		return false;
	}
}

解题思路:解法不唯一,我这里采用的是全排列的方式去解答,唯一的要点在于剪枝,因为其排列个数达到了16个,一般情况下,排列个数超过10个时,一定要进行剪枝,否则会超时。剪枝思路是检查当前节的周围是否有等于前一节的位置(周围指上下左右相邻,不包括对角相邻)。


试题 F: 蓝肽子序列

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

  • 【问题描述】

L 星球上的生物由蛋蓝质组成,每一种蛋蓝质由一类称为蓝肽的物资首尾连接成一条长链后折叠而成。
生物学家小乔正在研究 L 星球上的蛋蓝质。
她拿到两个蛋蓝质的蓝肽序列,想通过这两条蓝肽序列的共同特点来分析两种蛋蓝质的相似性。
具体的,一个蓝肽可以使用 1 至 5 个英文字母表示,其中第一个字母大写,后面的字母小写。
一个蛋蓝质的蓝肽序列可以用蓝肽的表示顺序拼接而成。
在一条蓝肽序列中,如果选取其中的一些位置,把这些位置的蓝肽取出,并按照它们在原序列中的位置摆放,则称为这条蓝肽的一个子序列。
蓝肽的子序列不一定在原序列中是连续的,中间可能间隔着一些未被取出的蓝肽。
如果第一条蓝肽序列可以取出一个子序列与第二条蓝肽序列中取出的某个子序列相等,则称为一个公共蓝肽子序列。
给定两条蓝肽序列,找出他们最长的那个公共蓝肽子序列的长度。

  • 【输入格式】

输入两行,每行包含一个字符串,表示一个蓝肽序列。字符串中间没有空格等分隔字符。

  • 【输出格式】

输出一个整数,表示最长的那个公共蓝肽子序列的长度。

  • 【样例输入】

LanQiaoBei
LanTaiXiaoQiao

  • 【样例输出】

2

  • 【样例说明】

最长的公共蓝肽子序列为 LanQiao,共两个蓝肽。

  • 【评测用例规模与约定】

对于 20% 的评测用例,两个字符串的长度均不超过 20。
对于 50% 的评测用例,两个字符串的长度均不超过 100。
对于所有评测用例,两个字符串的长度均不超过 1000。

个人代码:

import java.util.ArrayList;
import java.util.Scanner;

public class _蓝肽子序列 {

	public static void main(String[] args) {
		Scanner sc = new Scanner(System.in);

		char[] c1 = sc.next().toCharArray();
		char[] c2 = sc.next().toCharArray();

		if (c1.length == 0 || c2.length == 0) {
			System.out.println(0);
			return;
		}

		Object[] a = get_arr(c1);
		Object[] b = get_arr(c2);

		//标准的动态规划(最长公共子序列,LCS)
		int[][] dp = new int[a.length + 1][b.length + 1];
		for (int i = 1; i < dp.length; ++i) {
			for (int j = 1; j < dp[0].length; ++j) {
				if (a[i - 1].equals(b[j - 1])) {
					dp[i][j] = dp[i - 1][j - 1] + 1;
				} else {
					dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]);
				}
			}
		}

		System.out.println(dp[dp.length - 1][dp[0].length - 1]);
	}
	
	//将相应的字符数组输出相应的字符串数组
	public static Object[] get_arr(char[] ch) {
		ArrayList<String> list = new ArrayList<String>();
		StringBuilder sb = new StringBuilder("");
		sb.append(ch[0]);
		for (int i = 1; i < ch.length; ++i) {
			if (ch[i] >= 'A' && ch[i] <= 'Z') {
				list.add(sb.toString());
				sb = new StringBuilder("");
			}
			sb.append(ch[i]);
		}
		list.add(sb.toString());
		return list.toArray();
	}

}

解题思路:首先按题目要求将字符串拆分为相应的字符串数组,然后按照最长公共子序列(LCS)的思想进行解答即可。


试题 G: 皮亚诺曲线距离

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

  • 【问题描述】

皮亚诺曲线是一条平面内的曲线。
下图给出了皮亚诺曲线的 1 阶情形,它是从左下角出发,经过一个 3 × 3 的方格中的每一个格子,最终到达右上角的一条曲线。
在这里插入图片描述
下图给出了皮亚诺曲线的 2 阶情形,它是经过一个 32 × 32 的方格中的每一个格子的一条曲线。它是将 1 阶曲线的每个方格由 1 阶曲线替换而成。
在这里插入图片描述
下图给出了皮亚诺曲线的 3 阶情形,它是经过一个 33 × 33 的方格中的每一个格子的一条曲线。它是将 2 阶曲线的每个方格由 1 阶曲线替换而成。
在这里插入图片描述
皮亚诺曲线总是从左下角开始出发,最终到达右上角。
我们将这些格子放到坐标系中,对于 k 阶皮亚诺曲线,左下角的坐标是(0, 0),右上角坐标是 (3^k - 1, 3^k - 1),右下角坐标是 (3^k - 1, 0),左上角坐标是(0, 3^k - 1)。
给定 k 阶皮亚诺曲线上的两个点的坐标,请问这两个点之间,如果沿着皮亚诺曲线走,距离是到少?

  • 【输入格式】

输入的第一行包含一个正整数 k,皮亚诺曲线的阶数。
第二行包含两个整数 x1, y1,表示第一个点的坐标。
第三行包含两个整数 x2, y2,表示第二个点的坐标。

  • 【输出格式】

输出一个整数,表示给定的两个点之间的距离。

  • 【样例输入】

1
0 0
2 2

  • 【样例输出】

8

  • 【样例输入】

2
0 2
0 3

  • 【样例输出】

13

  • 【评测用例规模与约定】

对于 30% 的评测用例,0 ≤ k ≤ 10。
对于 50% 的评测用例,0 ≤ k ≤ 20。
对于所有评测用例,0 ≤ k ≤ 100, 0 ≤ x1, y1, x2, y2 < 3^k, x1, y1, x2, y2 ≤ 10^18。
数据保证答案不超过 10^18。


试题 H: 画廊

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

  • 【问题描述】

小蓝办了一个画展,在一个画廊左右两边陈列了他自己的作品。为了使画展更有意思,小蓝没有等距陈列自己的作品,而是按照更有艺术感的方式陈列。
在画廊的左边陈列了 L 幅作品,在画廊的右边陈列了 R 幅作品,左边的作品距离画廊的起点依次为 u1, u2, · · · , uL,右边的作品距离画廊起点依次为 v1, v2, · · · , vR。
每周,小蓝要整理一遍自己的每一幅作品。整理一幅作品的时间是固定的,但是要带着沉重的工具。从一幅作品到另一幅作品之间的距离为直线段的长度。
小蓝从画廊的起点的正中央(左右两边的中点)出发,整理好每一幅画,最终到达画廊的终点的正中央。已知画廊的宽为 w。
请问小蓝最少带着工具走多长的距离?

  • 【输入格式】

输入的第一行包含四个整数 L, R, d, w,表示画廊左边和右边的作品数量,以及画廊的长度和宽度。
第二行包含 L 个正整数 u1, u2, · · · , uL,表示画廊左边的作品的位置。
第三行包含 R 个正整数 v1, v2, · · · , vR,表示画廊右边的作品的位置。

  • 【输出格式】

输出一个实数,四舍五入保留两位小数,表示小蓝最少带着工具走的距离。

  • 【样例输入】

3 3 10 2
1 3 8
2 4 6

  • 【样例输出】

14.71

  • 【样例说明】

小蓝从起点开始,首先到达左边第一幅作品(走动距离 √2),然后到达左边第二幅作品(走动距离 2),然后到达右边第一幅作品(走动距离 √5),然后到达右边第二幅和第三幅作品(走动距离 2 和 2),然后到达左边第三幅作品(走动距离 2 √2),最后到达画廊终点(走动距离 √5)。
总共距离为 √2 + 2 + √5 + 2 + 2 + 2 √2 + √5 ≈ 14.71。

  • 【评测用例规模与约定】

对于 40% 的评测用例,1 ≤ L, R ≤ 10, 1 ≤ d ≤ 100, 1 ≤ w ≤ 100。
对于 70% 的评测用例,1 ≤ L, R ≤ 100, 1 ≤ d ≤ 1000, 1 ≤ w ≤ 1000。
对于所有评测用例,1 ≤ L, R ≤ 500, 1 ≤ d ≤ 100000, 1 ≤ w ≤ 100000, 0 ≤ u1 < u2 < · · · < uL ≤ d, 0 ≤ v1 < v2 < · · · < vR ≤ d。

个人代码:

import java.util.Scanner;

public class _画廊 {
	
	static int L, R, d, w;
	static int[] left, right;
	
	public static void main(String[] args) {
		Scanner sc = new Scanner(System.in);
		
		L = sc.nextInt();
		R = sc.nextInt();
		d = sc.nextInt();
		w = sc.nextInt();
		left = new int[L];
		right = new int[R];
		
		for(int i=0; i<L; ++i)
			left[i] = sc.nextInt();
		for(int i=0; i<R; ++i)
			right[i] = sc.nextInt();
		
		double res = 0;
		int cl = 0, cr = 0;
		boolean ping;
		if(left[cl]*left[cl] < right[cr]*right[cr]) {
			res += Math.sqrt((w/2.0)*(w/2.0) + left[cl]*left[cl]);
			++ cl;
			ping = true;
		}else {
			res += Math.sqrt((w/2.0)*(w/2.0) + right[cr]*right[cr]);
			++ cr;
			ping = false;
		}
		
		
		for(int i=1; i<L+R; ++i) {
			if(cl!=L && cr!=R) {
				if(ping) {
					double tl = left[cl]-left[cl-1], tr = Math.sqrt(w*w + Math.abs(left[cl-1]-right[cr])*Math.abs(left[cl-1]-right[cr]));
					if(tl < tr) {
						++ cl;
						res += tl;
					}else {
						++ cr;
						res += tr;
						ping = false;
					}
				}else {
					double tr = right[cr]-right[cr-1], tl = Math.sqrt(w*w + Math.abs(right[cr-1]-left[cl])*Math.abs(right[cr-1]-left[cl]));
					if(tl < tr) {
						++ cl;
						res += tl;
						ping = true;
					}else {
						++ cr;
						res += tr;
					}
				}	
			}else if(cl != L) {
				res += Math.sqrt(w*w + Math.abs(right[cr-1]-left[cl])*Math.abs(right[cr-1]-left[cl]));
				res += left[L-1]-left[cl];
				ping = true;
			}else {
				res += Math.sqrt(w*w + Math.abs(left[cl-1]-right[cr])*Math.abs(left[cl-1]-right[cr]));
				res += right[R-1]-right[cr];
				ping = false;
			}
		}
		if(ping) {
			res += Math.sqrt((w/2.0)*(w/2.0) + (d-left[L-1])*(d-left[L-1]));
		}else {
			res += Math.sqrt((w/2.0)*(w/2.0) + (d-right[R-1])*(d-right[R-1]));
		}
		
		System.out.println(String.format("%.2f", res));
	}

}

解题思路:这道题我采用的是贪心算法,不知道对不对。具体是检查左右两边“还在等待整理的第一幅作品”(注意是“还在等待”,如果已经整理过,则距离起点最近的未整理的作品成为“还在等待整理的第一幅作品”)距离小蓝当前的位置,哪边距离短则走哪边。


试题 I: 补给

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

  • 【问题描述】

小蓝是一个直升飞机驾驶员,他负责给山区的 n 个村庄运送物资。
每个月,他都要到每个村庄至少一次,可以多于一次,将村庄需要的物资运送过去。
每个村庄都正好有一个直升机场,每两个村庄之间的路程都正好是村庄之间的直线距离。
由于直升机的油箱大小有限,小蓝单次飞行的距离不能超过 D。
每个直升机场都有加油站,可以给直升机加满油。
每个月,小蓝都是从总部出发,给各个村庄运送完物资后回到总部。如果方便,小蓝中途也可以经过总部来加油。
总部位于编号为 1 的村庄。
请问,要完成一个月的任务,小蓝至少要飞行多长距离?

  • 【输入格式】

输入的第一行包含两个整数 n, D,分别表示村庄的数量和单次飞行的距离。
接下来 n 行描述村庄的位置,其中第 i 行两个整数 xi, yi,表示编号为 i 的村庄的坐标。村庄 i 和村庄 j 之间的距离为 √((xi - xj)^2 + (yi - yj)^2)。

  • 【输出格式】

输出一行,包含一个实数,四舍五入保留正好 2 位小数,表示答案。

  • 【样例输入】

4 10
1 1
5 5
1 5
5 1

  • 【样例输出】

16.00

  • 【样例说明】

四个村庄形成一个正方形的形状。

  • 【样例输入】

4 6
1 1
4 5
8 5
11 1

  • 【样例输出】

28.00

  • 【样例说明】

补给顺序为 1 → 2 → 3 → 4 → 3 → 2 → 1。

  • 【评测用例规模与约定】

对于所有评测用例,1 ≤ n ≤ 20, 1 ≤ xi, yi ≤ 10^4, 1 ≤ D ≤ 10^5。

个人代码:

import java.util.Arrays;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.Queue;
import java.util.Scanner;

public class _补给 {
	static int n, D;
	static int[][] v;
	static HashSet<Point>[] set;
	static double res = Double.MAX_VALUE;
	
	public static void main(String[] args) {
		Scanner sc = new Scanner(System.in);
		n = sc.nextInt();
		D = sc.nextInt();
		if(n == 1) {
			System.out.println(0);
			return ;
		}
		v = new int[n+1][2];		//使用数组保存每个村庄的位置
		for(int i=1; i<=n; ++i) {
			v[i][0] = sc.nextInt();
			v[i][1] = sc.nextInt();
		}
		set = new HashSet[n+1];
		for(int i=0; i<=n; ++i) {
			set[i] = new HashSet<Point>();
		}
		for(int i=1; i<=n; ++i) {
			for(int j=i+1; j<=n; ++j) {		//计算任意两个村庄的距离
				double t = Math.sqrt((v[i][0]-v[j][0])*(v[i][0]-v[j][0])+(v[i][1]-v[j][1])*(v[i][1]-v[j][1]));	
				if(t<=D) {			//只有距离小于或等于D的,才允许记录(飞机才能飞)
					set[i].add(new Point(i, j, t));
					set[j].add(new Point(j, i, t));
				}
			}
		}
		
		spfa();		//求总部到任意村庄的最短路即可充当任意村庄回到总部的最短路
		
		pi = new int[n+1];
		Arrays.fill(pi, -1);	
		pi[1] = 1;
		for(Point p : set[1]) {		//注意回溯
			pi[p.to] = 1;
			dfs(p.to, p.dis, 2, p.to);		
			pi[p.to] = -1;
		}
		System.out.println(String.format("%.2f", res));
	}
	
	//spfa算法模板
	static double[] vul;
	public static void spfa() {
		Queue<Integer> que = new LinkedList<Integer>();
		que.add(1);
		vul = new double[n+1];
		Arrays.fill(vul, Double.MAX_VALUE);
		vul[1] = 0;
		while(!que.isEmpty()) {
			int p = que.poll();
			for(Point t : set[p]) {
				if(vul[t.to]>vul[p]+t.dis) {
					vul[t.to]=vul[p]+t.dis;
					if(!que.contains(t.to)) {
						que.add(t.to);
					}
				}
			}
		}
	}
	
	static int[] pi;	//pi[i]代表第i号村庄的飞行历史令牌号
	//cur代表飞机当前所在的村庄,route代表飞机已经飞行过的路程,num代表已经有多少个村庄拿到了物质,ci代表飞机当前的令牌号
	public static void dfs(int cur, double route, int num, int ci) {
		if(num == n) {				
			res = Math.min(res, route+vul[cur]);
			return ;
		}
		for(Point p : set[cur]) {
			if(pi[p.to] != ci) {		//注意回溯
				int t = pi[p.to];
				pi[p.to] = ci;
				dfs(p.to, route+p.dis, t==-1?num+1:num, t==-1?p.to : ci);	//如果飞行历史令牌号为-1,则要对num加一,并且修改飞机的令牌号
				pi[p.to] = t;
			}				
		}
	}

	static class Point{
		int from, to;
		double dis;
		
		public Point(int f, int t, double d) {
			this.from = f;
			this.to = t;
			this.dis = d;
		}
	}
}

解题思路:这题有意思的点是可以重复经过任意村庄,这就意味着如果不采用限制,必然导致死循环,并其这题也是一道求最短路的。我的解法是分两步计算,第一步计算从总部出发,向所有村庄运送物质,第二步是从最后一个获取到物质的村庄出发,回到总部。第二步是很好计算的,利用SPFA算法求总部到任意村庄的最短路即可充当任意村庄回到总部的最短路。而第一步则比较麻烦,必须要避免死循环,我的做法是,飞机带有一个“令牌号”(这个令牌号为村庄的号码,初始值为1,因为1号村庄为总部),并且每个村庄都有一个历史飞行令牌号(初始值为-1,表示飞机还没来过),每次飞行前都先进行检查,检查目标村庄的历史飞行令牌号,首先如果历史飞行令牌号为-1,则允许飞行到该村庄,并将该村的历史飞行令牌号改为飞机当前带着的令牌号,然后将飞机带着的令牌号改为该村的号码;如果该村的历史飞行令牌号与飞机当前带着的令牌号不相等,也允许飞行到该村庄,并将该村的历史飞行令牌号改为飞机当前带着的令牌号(注意,这个条件下没有改变飞机带着的令牌号);反之,则不允许飞行到该村;


试题 J: 质数行者

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

  • 【问题描述】

小蓝在玩一个叫质数行者的游戏。
游戏在一个 n × m × w 的立体方格图上进行,从北到南依次标号为第 1 行到第 n 行,从西到东依次标号为第 1 列到第 m 列,从下到上依次标号为第 1 层到第 w 层。
小蓝要控制自己的角色从第 1 行第 1 列第 1 层移动到第 n 行第 m 列第 w层。
每一步,他可以向东走质数格、向南走质数格或者向上走质数格。每走到一个位置,小蓝的角色要稍作停留。
在游戏中有两个陷阱,分别为第 r1 行第 c1 列第 h1 层和第 r2 行第 c2 列第h2 层。
这两个陷阱的位置可以跨过,但不能停留。
也就是说,小蓝不能控制角色某一步正好走到陷阱上,但是某一步中间跨过了陷阱是允许的。
小蓝最近比较清闲,因此他想用不同的走法来完成这个游戏。
所谓两个走法不同,是指小蓝稍作停留的位置集合不同。
请帮小蓝计算一下,他总共有多少种不同的走法。
提示:请注意内存限制,如果你的程序运行时超过内存限制将不得分。

  • 【输入格式】

输入第一行包含两个整数 n, m, w,表示方格图的大小。
第二行包含 6 个整数,r1, c1, h1, r2, c2, h2,表示陷阱的位置。

  • 【输出格式】

输出一行,包含一个整数,表示走法的数量。答案可能非常大,请输出答
案除以 1000000007 的余数。

  • 【样例输入】

5 6 1
3 4 1 1 2 1

  • 【样例输出】

11

  • 【样例说明】

用 (r, c, h) 表示第 r 行第 c 列第 h 层,可能的走法有以下几种:

  1. (1, 1, 1) ) (1, 3, 1) ) (1, 6, 1) ) (3, 6, 1) ) (5, 6, 1)。
  2. (1, 1, 1) ) (1, 3, 1) ) (3, 3, 1) ) (3, 6, 1) ) (5, 6, 1)。
  3. (1, 1, 1) ) (1, 3, 1) ) (3, 3, 1) ) (5, 3, 1) ) (5, 6, 1)。
  4. (1, 1, 1) ) (3, 1, 1) ) (3, 3, 1) ) (3, 6, 1) ) (5, 6, 1)。
  5. (1, 1, 1) ) (3, 1, 1) ) (3, 3, 1) ) (5, 3, 1) ) (5, 6, 1)。
  6. (1, 1, 1) ) (3, 1, 1) ) (5, 1, 1) ) (5, 3, 1) ) (5, 6, 1)。
  7. (1, 1, 1) ) (3, 1, 1) ) (5, 1, 1) ) (5, 4, 1) ) (5, 6, 1)。
  8. (1, 1, 1) ) (1, 4, 1) ) (1, 6, 1) ) (3, 6, 1) ) (5, 6, 1)。
  9. (1, 1, 1) ) (1, 6, 1) ) (3, 6, 1) ) (5, 6, 1)。
  10. (1, 1, 1) ) (3, 1, 1) ) (3, 6, 1) ) (5, 6, 1)。
  11. (1, 1, 1) ) (3, 1, 1) ) (5, 1, 1) ) (5, 6, 1)。
  • 【评测用例规模与约定】

对于 30% 的评测用例 1 ≤ n, m,w ≤ 50。
对于 60% 的评测用例 1 ≤ n, m,w ≤ 300。
对于所有评测用例,1 ≤ n, m, w ≤ 1000,1 ≤ r1,r2 ≤ n, 1 ≤ c1, c2 ≤ m, 1 ≤ h1, h2 ≤ w,陷阱不在起点或终点,两个陷阱不同。


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值