题目描述
罗密欧与朱丽叶的迷宫。罗密欧与朱丽叶身处一个m x n 的迷宫中,如图所示。每一个方恪表示迷宫中的一个房间。这m x n个房间中有一些房间是封闭的。不允任何人进入。在迷宫中任何位置均可沿8个方向进入未封闭的房间。罗密欧位于迷宫的(p,q)方格中,他必须找出一条通向朱丽叶所在的(r,s)格的路,在朱丽叶方格之前,他必须走遍所有未封闭的房间各一次,而且要使到达朱丽叶方格的转弯次数为最少。 每改变一次前进方向算作转弯一次。请设计一个算法帮助罗密欧找出这样一条道路。
测试样例
输入:
第一行有3个正整数n,m,k,分别表示迷宫的行数,列数和封闭的房间数。接下来的k行中,每行2个正整数,表示被封闭的房间所在的行号和列号。最后的2行,每行也有2个正整数,分别表示罗密欧所处的方格(p,q)和朱丽叶所处的方格(r,s)。
3 4 2
1 2
3 4
1 1
2 2
输出:
将计算出的罗密欧通向朱丽叶的最少转弯次数和有多少条不同的最少转弯道路。文件的第一行是最少转弯次数。文件的第2行是不同的最少转弯道路数。接下来的n行每行m个数,表示迷宫的一条最少转弯道路。A[i][j]=k表示第k步到达方格(i,j);A[i][j]=-1 表示方格(i,j)是封闭的。
6
7
1 -1 9 8
2 10 6 7
3 4 5 -1
算法原理
这道题相比于以往的回溯算法模式相同,但编写难度更高。对于这道题的分析还是从终止条件和递归体两部分着手:
- 终止条件:当遍历的房间数等于所有未封闭房间数且到达目的地(x == r && y == s && roads == (m * n- k))时本轮递归就结束了,再判别本轮递归是否转弯次数更少,更新答案。
- 递归体:对于每次选择下一个点我们都有八个方位可以去(上,下,左,右, 左上,右上,左下,右下),那么每轮递归我们只需要依次遍历八个方位即可,不能重复遍历也不能走入非法空间(超出空间范围或进入封闭房间)。由于是回溯算法且有很多变量是全局变量,我们必须在递归后恢复所有数值,不能影响下一步遍历。
- 剪枝条件(可选):通过中间结果判别直接裁剪掉这部分解空间,可以加快运算效率。由于我们要求最少的转弯次数,所以当中间某个走法的转弯次数已经比之前记录过的转弯次数更多的时候就没有必要再继续运算了,进行剪枝。
对于任何一个回溯法题解,你必须想到其递归终止条件,以及在递归体中用什么样的顺序遍历所有空间。通常还需要考虑是否要设置一个visited数组以保证不重复遍历,本题由于图所蕴含的信息非常少,就直接使用承载输入信息(封闭空间位置)的图(Map)用来记录遍历信息。
算法实现
#include<bits/stdc++.h>
#define INF 0x3f3f3f3f
using namespace std;
static int Map[100][100], m, n, k, p, q, r, s, minz = INF, roadnums = 0, minMap[100][100];
static int diry[8] = { 0, 0, -1, 1, -1, -1, 1, 1 }, dirx[8] = { -1, 1, 0, 0, -1, 1, -1, 1 };
//方向(dirx[i], diry[i])
static int judge(int x, int y) { //判断点是否有效
if (x > 0 && x <= m && y > 0 && y <= n && Map[x][y] != -1)
return 1;
else
return 0;
}
//方向标志dir:0, 1, 2, 3, 4,5,6,7,8->(初始,上,下,左,右, 左上,右上,左下,右下)
static void dfs(int x, int y, int znums, int roads, int dir, int pace) {//(x, y)当前方位,znums转弯次数,roads访问房间数,pace当前是第几步
if (x == r && y == s) { //见到朱丽叶
if (roads == (m * n- k)) { //走完了所有非封闭房间且转弯次数更少
if (znums < minz) {
roadnums = 1;//最少转弯道路数更新
minz = min(minz, znums);
for (int i = 1; i <= m; i++)
for (int j = 1; j <= n; j++)
minMap[i][j] = Map[i][j];
}
else if (znums == minz)//不同的最少转弯道路数
roadnums++;
}
return;
}
for (int i = 0; i < 8; i++) {
int nx = x + dirx[i], ny = y + diry[i];
int j = judge(nx, ny);
if (Map[nx][ny] || !j)//不走回头路且只走非封闭房间
continue;
Map[nx][ny] = ++pace;
int curdir = i+1;//判断是否拐弯
if (dir && dir != curdir)
znums++;
if (znums > minz)//剪枝
return;
int tempdir = dir;//暂存数值,为了回溯时复原
dir = curdir;
dfs(nx, ny, znums, roads+1, dir, pace);
Map[nx][ny] = 0;
if(dir!= tempdir)
znums--;
dir = tempdir;
pace--;
}
}
void main()
{
memset(Map, 0, sizeof(Map));
cin >> m >> n >> k;
for (int i = 0; i < k; i++) {
int temp1, temp2;
cin >> temp1 >> temp2;
Map[temp1][temp2] = -1; //封闭房间
}
cin >> p >> q >> r >> s;
Map[p][q] = 1;
dfs(p, q, 0, 1, 0, 1);
cout << minz << "\n" << roadnums << endl;;
for (int i = 1; i <= m; i++) {
for (int j = 1; j <= n; j++)
cout << minMap[i][j] << "\t";
cout << endl;
}
}
/*
3 4 2
1 2
3 4
1 1
2 2
*/