马的管辖-----深搜代码

7 篇文章 0 订阅
7 篇文章 0 订阅

马的管辖

结果填空:马的管辖 - 题库 - 计蒜客 (jisuanke.com)

在中国象棋中,马是走日字的。一个马的管辖范围指的是当前位置以及一步之内能走到的位置,下图的绿色旗子表示马能走到的位置。

如果一匹马的某个方向被蹩马脚,它就不能往这个方向跳了,如下图所示,海星的位置存在旗子,马就不能往上跳到那两个位置了:

那么问题来了,在一个 n\times mn×m 的棋盘内,如何用最少的马管辖住所有 n\times mn×m 个格子。比如 n=m=3n=m=3 时,最少要用 55 只马才能管辖所有棋盘,一种可能的方案如下: 

当 n=m=5n=m=5 时,请你求出用最少马管辖的 方案个数

 

思路:就是全排了,暴力法可以弄出来,不过查了一下没有深搜,所以 写一下深搜思路

用二维数组比较方便,先初始后一个二维数组m*m

static int map[][] = new int[m][m];
static int vmap[][] = new int[m][m];

vmap[][]是虚拟地图,用来记录map[][]对应是否被标记过

接下来是dfs代码:

	static void dfs(int step) {
	if (step == m * m) {
		if (targetloop()) {
			for (int i = 0; i < m; i++) {
				for (int j = 0; j < m; j++) {
					System.out.print(map[i][j]);
				}
				System.out.println();
			}
			System.out.println();
			}
		return;
	}

		for (int j2 = 0; j2 <= 1; j2++) {
			int x = step / m;
			int y = step % m;
			if (vmap[x][y] == 0) {
				vmap[x][y] = 1;
				map[x][y] = j2;
				dfs(step + 1);
				vmap[x][y] = 0;
			}
		}
	}

返回点肯定是step=m*m,这里for循环0和1,0相当于空位,1相当于马位,x坐标等step/m,y坐标等于step%m,这样我们就能跟着step来一层层递归,targetloop()是检测,检测是否填满检测是否最小方案(记得检测是否憋马脚),如果检测通过则输出,下面重点在targetloop。

static boolean targetloop() {
			boolean sum0 = true;
			int sum1 = 0;
			int dir2[][] = { { -1, 0 }, { 0, -1 }, { 1, 0 }, { 0, 1 } };//憋马脚的位置,逆时针存放,对应dir顺序
			int dir[][] = { { -2, -1 }, { -2, 1 }, { -1, -2 }, { 1, -2 }, { 2, -1 }, { 2, 1 }, { -1, 2 }, { 1, 2 }};//马可以跳的位置,逆时针存放,待会检测憋马脚也是逆时针检测,对应dir2顺序
			int testmap[][] = new int[m][m];//再来个测试地图,记录每个马位置(标记1)和马可以跳的位置(标记2)
			for (int i = 0; i < map.length; i++) {
				for (int j = 0; j < map.length; j++) {
					if (map[i][j] == 1) {
						testmap[i][j] = 1;    //真实地图马位赋值给测试地图
					}
				}
			}
			for (int i = 0; i < testmap.length; i++) {
				for (int j = 0; j < testmap.length; j++) {
					if (testmap[i][j] == 1) {
						for (int k = 0; k < dir.length; k++) { //检测8个马可以跳方向
							int xx = dir[k][0] + i;//当前马跳的坐标x
							int yy = dir[k][1] + j;//当前马跳的坐标y
							if (xx >= 0 && xx < m && yy >= 0 && yy < m) { //边界判断
								//检测马跳位置的马脚是否憋住
								int xxx = dir2[k / 2][0] + i;///当前检测憋马脚的坐标x,这里的k对应马跳位置的顺序一个方向可以憋两个位置
								int yyy = dir2[k / 2][1] + j;///当前检测憋马脚的坐标y,这里的k对应马跳位置的顺序一个方向可以憋两个位置
								if (xxx >= 0 && xxx < m && yyy >= 0 && yyy < m) {
									if (testmap[xxx][yyy] != 1) {
										if (testmap[xx][yy]==0) {
											testmap[xx][yy] = 2; //如果没有憋住也有空位,则为2,标记可以跳
										}
									}
								}
							}
						}
					}
				}
			}
			
			for (int i = 0; i < testmap.length; i++) {
				for (int j = 0; j < testmap.length; j++) {
					if (testmap[i][j] == 0) { //检测是否把空位填满
						sum0 = false;
					}
					if (testmap[i][j] == 1) {
						sum1++;  //计算马的数量
					}
				}
			}
			if (sum0) {
				if (min >= sum1) {  //检测马数是否是最小,因为递归是逆推的,所以默认是从最小的开始排列,只要记录最小可以满足方案马的数量,则一直按照最小数量那个来就行
					min = sum1; //记录最小马位
					for (int i = 0; i < testmap.length; i++) {
						for (int j = 0; j < testmap.length; j++) {
							System.err.print(testmap[i][j]);
						}
						System.err.println();
					}
					System.out.println();
					loopsum++;//马的满足方案+1
					return true;
				}
			}
			return false;
	}

重点在马跳的位置和憋马位置,方向要对应,最后计算是否被马和马对应跳的位置铺满即可(记得检测一下是否为最小马的数量)。

 

 

完整代码如下:

public class Main {
	static int min = Integer.MAX_VALUE;
	static int m = 5;//可以自行修改  测试m*m地图
	static int loopsum = 0;
	static int map[][] = new int[m][m];
	static int vmap[][] = new int[m][m];

	public static void main(String[] args){

		for (int i = 0; i < m; i++) {
			for (int j = 0; j < m; j++) {
				map[i][j] = 0;
			}
		}
		dfs(0);
		 System.err.println(loopsum);
	}

	static void dfs(int step) {
		if (step == m * m) {
			 if (targetloop()) {
			for (int i = 0; i < m; i++) {
				for (int j = 0; j < m; j++) {
					System.out.print(map[i][j]);
				}
				System.out.println();
			}
			System.out.println();
			}
			 return;
		}

		for (int j2 = 0; j2 <= 1; j2++) {
			int x = step / m;
			int y = step % m;
			if (vmap[x][y] == 0) {
				vmap[x][y] = 1;
				map[x][y] = j2;
				dfs(step + 1);
				vmap[x][y] = 0;
			}
		}
	}

	static boolean targetloop() {
			boolean sum0 = true;
			int sum1 = 0;
			int dir2[][] = { { -1, 0 }, { 0, -1 }, { 1, 0 }, { 0, 1 } };//憋马脚的位置,逆时针存放,对应dir顺序
			int dir[][] = { { -2, -1 }, { -2, 1 }, { -1, -2 }, { 1, -2 }, { 2, -1 }, { 2, 1 }, { -1, 2 }, { 1, 2 }};//马可以跳的位置,逆时针存放,待会检测憋马脚也是逆时针检测,对应dir2顺序
			int testmap[][] = new int[m][m];//再来个测试地图,记录每个马位置(标记1)和马可以跳的位置(标记2)
			for (int i = 0; i < map.length; i++) {
				for (int j = 0; j < map.length; j++) {
					if (map[i][j] == 1) {
						testmap[i][j] = 1;    //真实地图马位赋值给测试地图
					}
				}
			}
			for (int i = 0; i < testmap.length; i++) {
				for (int j = 0; j < testmap.length; j++) {
					if (testmap[i][j] == 1) {
						for (int k = 0; k < dir.length; k++) { //检测8个马可以跳方向
							int xx = dir[k][0] + i;//当前马跳的坐标x
							int yy = dir[k][1] + j;//当前马跳的坐标y
							if (xx >= 0 && xx < m && yy >= 0 && yy < m) { //边界判断
								//检测马跳位置的马脚是否憋住
								int xxx = dir2[k / 2][0] + i;///当前检测憋马脚的坐标x,这里的k对应马跳位置的顺序一个方向可以憋两个位置
								int yyy = dir2[k / 2][1] + j;///当前检测憋马脚的坐标y,这里的k对应马跳位置的顺序一个方向可以憋两个位置
								if (xxx >= 0 && xxx < m && yyy >= 0 && yyy < m) {
									if (testmap[xxx][yyy] != 1) {
										if (testmap[xx][yy]==0) {
											testmap[xx][yy] = 2; //如果没有憋住也有空位,则为2,标记可以跳
										}
									}
								}
							}
						}
					}
				}
			}
			
			for (int i = 0; i < testmap.length; i++) {
				for (int j = 0; j < testmap.length; j++) {
					if (testmap[i][j] == 0) { //检测是否把空位填满
						sum0 = false;
					}
					if (testmap[i][j] == 1) {
						sum1++;  //计算马的数量
					}
				}
			}
			if (sum0) {
				if (min >= sum1) {  //检测马数是否是最小,因为递归是逆推的,所以默认是从最小的开始排列,只要记录最小可以满足方案马的数量,则一直按照最小数量那个来就行
					min = sum1; //记录最小马位
					for (int i = 0; i < testmap.length; i++) {
						for (int j = 0; j < testmap.length; j++) {
							System.err.print(testmap[i][j]);
						}
						System.err.println();
					}
					System.out.println();
					loopsum++;//马的满足方案+1
					return true;
				}
			}
			return false;
	}
}

跑出结果还是需要十几秒,就当练习一下全排, 最后答案:90 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值