问题 E: 中国象棋中的跳马问题
题目描述
现在棋盘的大小不一定,由p,q给出,并且在棋盘中将出现障碍物(限制马的行动,与象棋走法相同) 输入 第一行输入n表示有n组测试数据。每组测试数据第一行输入2个整数p,q,表示棋盘的大小(1<=p,q<=100)。 每组测试数据第二行输入4个整数,表示马的起点位置与终点位置。(位置的取值范围同p,q) 第三行输入m表示图中有多少障碍。 接着跟着m行,表示障碍的坐标。
输出
马从起点走到终点所需的最小步数。 如果马走不到终点,则输入“can not reach!”
样例输入 Copy
2
9 10
1 1 2 3
0
9 10
1 1 2 3
8
1 2
2 2
3 3
3 4
1 4
3 2
2 4
1 3
样例输出 Copy
1
can not reach!
提示
此题是一个搜索题,可用DFS或BFS,建议选择BFS(广搜)。一开始把马的起始点加入队列,然后用广搜的思想把此点能到达的其他点加入队列,这里需要一个数组用来记录此点在之前是否已经加入队列,如果加入过队列当中,就不需要再加入了,直到队列里的元素为空,或者搜索到了终点,搜索即停止,然后输出相应答案即可。
思路:
1、输入样例个数n,用while实现个数。
2、输入棋盘大小。
3、输入起点与终点,设置障碍坐标。
4、使用广度优先搜索,队列,初始化一个数组代表8个方向。
5、最小步数:遍历完每一层num_foot++。
6、走不到终点:队列为空,输出 “can not reach!”。
实现代码:
#include<bits/stdc++.h>
using namespace std;
//输出chess数组的函数,可以测试用
// void print_chess(int *chess, int w, int h){
// cout << "---------------------------------------------------" << endl;
// string show[] = {" .", " +", " 0"};
// for (int i = 0; i <= w; ++i)
// {
// cout << " " << (i%10);
// }
// cout << endl;
// for (int i = 1; i <= w; ++i)
// {
// cout << (" ") << (i%10);
// for (int j = 1; j <= h; ++j)
// {
// cout << show[ chess[i*(w+1)+j] ];
// }
// cout << endl;
// }
// }
int main(int argc, char const *argv[])
{
int n;
cin >> n; // 1、样例个数n
whi:
while(n--)
{
int w,h;
cin >> w >> h; // 2、棋盘的大小w,h
int base = max(w,h) + 1; /*防止取余和取模时候有歧义,首先要取 w,h 的最大值,再+1,(举例)*/
int chess[w+1][h+1] = {0}; // chess二维数组赋值为0
for (int i = 0; i <= w; ++i)
{
for (int j = 0; j <= h; ++j)
{
chess[i][j] = 0; /*初始化问题*/
}
}
int x1,y1,x2,y2; //3、 start = (x1,y1) ,end = (x2,y2)
cin >> x1 >> y1 >> x2 >> y2 ;
int num_barr; //障碍个数
cin >> num_barr;
while(num_barr--)
{
int xb,yb;
cin >> xb >> yb ;
chess[xb][yb] = 2; //在棋盘中,障碍的坐标赋值为2
}
int start = x1 *base + y1; //把坐标储存在一个数当中
int end = x2 * base + y2;
int num_foot = 0;
queue <int > q;
q.push(start);
chess[x1][y1] = 1;
while(q.size()) //4、广度优先搜索
{
int queue_size = q.size();
while(queue_size--) //5、遍历每一层
{
int front = q.front() ; q.pop();
int x = front / base ;
int y = front % base ;
if(x == end/base && y == end%base)
{
cout << num_foot << endl;
goto whi;
}
int _x[8] = {2, 2, 1, -1, -2, -2, -1, 1};
int _y[8] = {1, -1, -2, -2, -1, 1, 2, 2};
for (int i = 0; i < 8; ++i)
{
int next_x = x + _x[i];
int next_y = y + _y[i];
int majiao_x = x + _x[i]/2;
int majiao_y = y + _y[i]/2;
if ( next_x>=1 && next_x<=w &&
next_y>=1 && next_y<=h &&
chess[majiao_x][majiao_y] != 2 &&
chess[next_x][next_y] == 0
){
chess[next_x][next_y] = 1;
q.push(next_x * base + next_y);
}
}
}
num_foot++;
}
// if(q.size()==0)
cout << "can not reach!" << endl; //6、队列为空,即把所有能走的都访问过,如果还到不了终点,即输出
}
return 0;
}
总结:
- 读完题知道这是一个广度优先搜索,常规需要 :存储题目所给内容的数组、vis[]数组、队列进行遍历等。而这道题我把数组合成一个chess[]数组,先进行初始化全部为0,如果访问过则改成1,障碍则为2,这样就可以减少数组的使用。
- 数组的初始化问题,最开始写的是int chess[w][h] = {0},这样其实没有很好的初始化,所以需要用两个for循环进行赋值,或者使用memset()函数也可。
- 题目中棋盘的大小p,q是从1开始的,要认真读题!!
- 运用取余和取模运算时,乘的n要是比较大的数,不然会有歧义。例:w=3,h=10,当坐标为(4,7)时,通过取余取模运算后坐标就变成了(6,1),因此可知乘的数要是w,h中较大的数,还要加一,不然也会有歧义。同样的道理,在从front取余取模变成坐标的时候,也应该是max(w,h)+1,我在最开始定义为了base,方便后边计算。
- 这道题是遍历完一层,只要没到达终点,步数(num_foot)就要加一,同样用到了一个巧妙的queue_size,因为每次入队是把下一层所有入队,再进行while循环,就可以遍历每一层之后num_foot++。
- 队列为空,即把所有能走的都访问过,如果还到不了终点,即输出can not reach!。