马的管辖
结果填空:马的管辖 - 题库 - 计蒜客 (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;
}
}