第七届蓝桥杯 JAVA B组决赛真题详解

感觉在这里码字不太习惯,所有的解题思路都在代码注释里面了

1、愤怒小鸟

X星球愤怒的小鸟喜欢撞火车!

一根平直的铁轨上两火车间相距 1000 米
两火车 (不妨称A和B) 以时速 10米/秒 相对行驶。

愤怒的小鸟从A车出发,时速50米/秒,撞向B车,
然后返回去撞A车,再返回去撞B车,如此往复…
两火车在相距1米处停车。

问:这期间愤怒的小鸟撞 B 车多少次?

注意:需要提交的是一个整数(表示撞B车的次数),不要填写任何其它内容。

源码

package 第七届蓝桥杯决赛真题;
//思路:这道题好像没找到别的方法,干脆就直接模拟全过程,反正这点计算量对计算机来说都不是事。
public class question1_1 {
	public static void main(String args[]) {
		float a=0;		//火车a的位置
		float b=1000;	//火车b的位置
		float c=0;			//小鸟的位置
		float speed1=10;		//火车速度
		float speed2=50;		//小鸟速度
		//模拟第一次发生撞击,每次撞击后更新当前状态信息,火车位置,小鸟位置,撞击时间
		float time=(b-c)/(speed1+speed2);			//撞击发生的时间
		a=speed1*time;								//撞击后a所在的位置
		b=1000-(speed1*time);						//撞击后b所在的位置
		c=speed2*time;								//撞击后c所在的位置
		int ans=1;									//已经撞击过一次
		int i=1;									//i=1表示要和b相撞,i=-1表示要和a相撞
		while ((b-a)>1) {
			i*=-1;				//小鸟和每个火车相撞后,都会更换目标
			if(i==1) {
				ans++;								//只在撞击B时才会将次数增加一
				time+=(b-c)/(speed1+speed2);
				c+=(b-c)/(speed1+speed2)*speed2;
				a=speed1*time;
				b=1000-(speed1*time);
			}
			if(i==-1) {
				time+=(c-a)/(speed1+speed2);
				c-=(c-a)/(speed1+speed2)*speed2;
				a=speed1*time;
				b=1000-(speed1*time);
			}			
		}
		System.out.printf("总共撞击B次数ans:%d\n", ans);
	}
}
答案:9

2、反幻方

我国古籍很早就记载着

2 9 4
7 5 3
6 1 8

这是一个三阶幻方。每行每列以及对角线上的数字相加都相等。

下面考虑一个相反的问题。
可不可以用 1~9 的数字填入九宫格。
使得:每行每列每个对角线上的数字和都互不相等呢?

这应该能做到。
比如:
9 1 2
8 4 3
7 5 6

你的任务是搜索所有的三阶反幻方。并统计出一共有多少种。
旋转或镜像算同一种。

比如:
9 1 2
8 4 3
7 5 6

7 8 9
5 4 1
6 3 2

2 1 9
3 4 8
6 5 7

等都算作同一种情况。

请提交三阶反幻方一共多少种。这是一个整数,不要填写任何多余内容。

旋转有四种
镜像4种
全排列除以8

源码

package 第七届蓝桥杯决赛真题;

import java.util.HashSet;
import java.util.Set;
/*
这道题就是利用全排列来求解,全排列的求解方法网上有一大堆,不做赘述
通过全排列和检查就可以解决这道问题
检查时有个比较好的方法:
行,列,斜线一共有八个,八个数字两两不相等有28种写法,不太好些,我们可以直接利用Set集和来判断,利用集和中重复元素只能存在一次的性质,通过集和长度来判断是否有相同元素
要注意的就是九宫格的旋转和镜像有8种状态,将计算出的结果除以八
旋转四种,旋转90°,180°,270°,加上自身,镜像四种有四条对称轴
 */
public class question2 {
	static int ans=0;
	public static void main(String args[]) {
		int[] num= {1,2,3,4,5,6,7,8,9};
		f(num,0,num.length-1);
		System.out.println(ans/8);		//可以将最后的结果对8求余来验证是否正确,求余结果一定为0
	}
	
	private static void f(int[] num, int start, int length) {
		if(start==num.length-1) {
			if (check(num)) {
				ans++;
			}
		}
		for (int i = start; i < num.length; i++) {
			swap(num, i, start);
			f(num,start+1,num.length);
			swap(num, i, start);
		}		
	}

	//检查,行,列,斜线一共有八个,八个数字两两不相等有28种,我们可以直接利用Set集和来判断,利用集和中重复元素只能存在一次的性质,通过集和长度来判断是否有相同元素
	private static boolean check(int[] num) {
		Set<Integer> set=new HashSet<Integer>();
		int a1=num[0]+num[1]+num[2];
		int a2=num[3]+num[4]+num[5];
		int a3=num[6]+num[7]+num[8];
		int a4=num[0]+num[3]+num[6];
		int a5=num[1]+num[4]+num[7];
		int a6=num[2]+num[5]+num[8];
		int a7=num[0]+num[4]+num[8];
		int a8=num[2]+num[4]+num[6];
		set.add(a1);
		set.add(a2);
		set.add(a3);
		set.add(a4);
		set.add(a5);
		set.add(a6);
		set.add(a7);
		set.add(a8);
		if(set.size()==8)
			return true;
		return false;
	}
	
	//交换数组中的两个指定下标的元素
	private static void swap(int[] a, int i, int start) {
		int b=a[i];
		a[i]=a[start];
		a[start]=b;		
	}
}

答案:3120

3、

打靶

小明参加X星球的打靶比赛。
比赛使用电子感应计分系统。其中有一局,小明得了96分。

这局小明共打了6发子弹,没有脱靶。
但望远镜看过去,只有3个弹孔。
显然,有些子弹准确地穿过了前边的弹孔。

不同环数得分是这样设置的:
1,2,3,5,10,20,25,50

那么小明的6发子弹得分都是多少呢?有哪些可能情况呢?

下面的程序解决了这个问题。
仔细阅读分析代码,填写划线部分缺失的内容。

public class Main
{
static void f(int[] ta, int[] da, int k, int ho, int bu, int sc)
{
if(ho<0 || bu<0 || sc<0) return;
if(k==ta.length){
if(ho>0 || bu>0 || sc>0) return;
for(int i=0; i<da.length; i++){
for(int j=0; j<da[i]; j++)
System.out.print(ta[i] + " ");
}
System.out.println();
return;
}

	for(int i=0; i<=bu; i++){
		da[k] = i;
		f(ta, da, k+1,  __________________ , bu-i, sc-ta[k]*i);   // 填空位置
	}
	
	da[k] = 0;
}

public static void main(String[] args)
{
	int[] ta = {1,2,3,5,10,20,25,50};
	int[] da = new int[8];
	f(ta, da, 0, 3, 6, 96);
}

}

注意:只填写划线处缺少的内容,不要填写已有的代码或符号,也不要填写任何解释说明文字等。

源码

//代码填空题,没什么好解释的,Debug就可以了,这道题知识有点绕
public class question3 {
	/*
	 * ta:代表不同环数的得分
	 * da:da[i],代表在第i环上打了da[i]枪
	 * k:表示接下来打来讨论第k环打了多少次
	 * ho:表示目前还剩下几个孔没讨论
	 * bu:表示目前打了6-bu枪,即还能打机枪
	 * sc:表示目前已经打了96-sc分
	 */
	static void f(int[] ta, int[] da, int k, int ho, int bu, int sc) {
		if (ho < 0 || bu < 0 || sc < 0)
			return;
		if (k == ta.length) {
			if (ho > 0 || bu > 0 || sc > 0)
				return;
			for (int i = 0; i < da.length; i++) {
				for (int j = 0; j < da[i]; j++)
					System.out.print(ta[i] + " ");
			}
			System.out.println();
			return;
		}
		for (int i = 0; i <= bu; i++) {
			da[k] = i;
			// 如果第ta[i]换不打,即i=0,则讨论下一枪的情况时,还有ho个孔没打;若i!=0,则讨论下一枪的情况时,有ho-1个孔还没打。
			f(ta, da, k + 1, ho - (i == 0 ? 0 : 1), bu - i, sc - ta[k] * i); // 填空位置
		}
		da[k] = 0;
	}
 
	public static void main(String[] args) {
		int[] ta = { 1, 2, 3, 5, 10, 20, 25, 50 };
		int[] da = new int[8];
		f(ta, da, 0, 3, 6, 96);
	}
}
答案:ho - (i == 0 ? 0 : 1)

4、路径之谜

小明冒充X星球的骑士,进入了一个奇怪的城堡。
城堡里边什么都没有,只有方形石头铺成的地面。

假设城堡地面是 n x n 个方格。【如图1.png】所示。

按习俗,骑士要从西北角走到东南角。
可以横向或纵向移动,但不能斜着走,也不能跳跃。
每走到一个新方格,就要向正北方和正西方各射一箭。
(城堡的西墙和北墙内各有 n 个靶子)

同一个方格只允许经过一次。但不必做完所有的方格。

如果只给出靶子上箭的数目,你能推断出骑士的行走路线吗?

有时是可以的,比如图1.png中的例子。

本题的要求就是已知箭靶数字,求骑士的行走路径(测试数据保证路径唯一)

输入:
第一行一个整数N(0<N<20),表示地面有 N x N 个方格
第二行N个整数,空格分开,表示北边的箭靶上的数字(自西向东)
第三行N个整数,空格分开,表示西边的箭靶上的数字(自北向南)

输出:
一行若干个整数,表示骑士路径。

为了方便表示,我们约定每个小格子用一个数字代表,从西北角开始编号: 0,1,2,3…
比如,图1.png中的方块编号为:

0 1 2 3
4 5 6 7
8 9 10 11
12 13 14 15

示例:
用户输入:
4
2 4 3 4
4 3 3 3

程序应该输出:
0 4 5 1 2 3 7 11 10 9 13 14 15

资源约定:
峰值内存消耗 < 256M
CPU消耗 < 1000ms

请严格按要求输出,不要画蛇添足地打印类似:“请您输入…” 的多余内容。

所有代码放在同一个源文件中,调试通过后,拷贝提交该源码。
注意:不要使用package语句。不要使用jdk1.7及以上版本的特性。
注意:主类的名字必须是:Main,否则按无效代码处理。

源码

package 第七届蓝桥杯决赛真题;
import java.util.Scanner;
/*
解题思路:
递归+回溯+判断
根据题目要去,我们要做的是寻找每一条可以从起点到达终点的路径,然后对我们查找到的路径进行分析比较,将正确的路径输出。

处理用户输入:
定义一个int变量n来表示地图的尺寸,声明两个int数据用来存储两边靶子上的箭的个数,作为全局变量,供检查的时候调用。

查找路径:
使用dfs(深度优先搜索)通过递归和回溯找到每一条路径,将路径上每一个格子对应的编号存储在数组中,根据题目要求,一个格子只能走一次,所以需要设置一个二维布尔数组吗,用来标记格子是否被走过,在回溯的时候要更改标记,
递归结束的条件是走到了终点。

检查路径
根据题意,每行走到一个格子,就要向两边的靶子上各射一箭,在遍历路径的时候,对于行走过的每一个格子,求出横纵坐标,将对应的两个靶子上的箭的个数增加1,这一步使用两个一位数组表示,最后和用户输入进行比较,即可判断当前路径是否正确
 */
public class question4 {
	static int[][] map;				//全局变量,地图,即每个格子的编号
	static boolean[][] flag;		//全局变量,标记格子是否被走过
	static int[] north;				//全局变量,存储用户输入,表示北边的箭靶
	static int[] south;				//全局变量,存储用户输入,表示北边的箭靶
	public static void main(String args[]) {
		//处理用户输入
		Scanner in=new Scanner(System.in);
		int n=in.nextInt();
		north=new int[n];
		south=new int[n];
		for (int i = 0; i < n; i++) {
			north[i]=in.nextInt();
		}
		for (int i = 0; i < n; i++) {
			south[i]=in.nextInt();
		}
		
		map=new int[n+1][n+1];
		flag=new boolean[n+1][n+1];
		for (int i = 0; i < n; i++) {				//初始化地图,即给地图进行编号
			for (int j = 0; j < n; j++) {
				map[i][j]=i*4+j;
			}
		}
		int[] path=new int[n*n];						//记录路径,地图的范围为n*n,所以路径最长为n*n
		int count=0;									//记录路径长度
		flag[0][0]=true;								
		path[count++]=map[0][0];						//0,0位置是起始位置,直接将其加入的路径中,并标记为已走过
		dfs(0,0,n,path,count);							//使用dfs算法解决本题
	}
	
	private static void dfs(int x, int y, int n, int[] path, int count) {			//x,y是当前位置,n是地图的长度,path是路径,count是路径长度
		int[] line= {1,0,-1,0};			
		int[] row= {0,1,0,-1};			//这两个数组表示每次可以走的范围,上下左右
		if(x==n-1&&y==n-1) {			//递归结束条件,走到终点
			//递归结束执行检查
			if(check(path,count,n)) {		//路径符合要求,输出
				for (int i = 0; i < count; i++) {
					System.out.print(path[i]+" ");
				}
				System.out.println("");
			}		
		}
		//递归四周的点
		for (int i = 0; i < 4; i++) {
			int now_x=x+line[i];			//求接下来一个节点的坐标
			int now_y=y+row[i];
			if(now_x<0||now_x>(n-1)||now_y<0||now_y>(n-1))		//位置不在地图内
				continue;
			if(flag[now_x][now_y])			//位置已经被经过
				continue;
			flag[now_x][now_y]=true;			//标记为已走过
			path[count++]=map[now_x][now_y];	//添加到路径当中
			dfs(now_x, now_y, n, path, count);	//递归寻找路径
			flag[now_x][now_y]=false;			//回溯,清空对这一个点的标记
			count-=1;							//路径长度减一,表示退出这个格子,将其在路径中删除
		}
		
	}

	//路径检查
	private static boolean check(int[] path, int count, int n) {
		int[] line=new int[n];		//表示当前这个路径射到的箭靶
		int[] row=new int[n];
 		for (int i = 0; i < count; i++) {		//求每一个格子对应的箭靶,更新箭靶状态
			int x=path[i]/4;
			int y=path[i]%4;
			line[y]+=1;
			row[x]+=1;
		}
		
 		boolean ans=true;		
		for (int i = 0; i < n; i++) {
			if(north[i]!=line[i]||south[i]!=row[i]) {		//如果有一个箭靶不符合要求,本条路径判断为不正确
				ans=false;
				continue;
			}
		}
		return ans;
	}
}

后两道题目前还没有找到较好的解决办法,如果有解决的话会分享出来。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值