洛谷P1126 机器人搬重物 详细注释
这道题,毫无疑问,就是广搜。但是它要注意的细节非常多。所以这道题对逻辑和代码能力的的考察很高
首先,这道题需要注意,障碍物是在格子上,而机器人是在格点上走。某人就是我自信地写完了代码后发现题读错了。关键最讨厌的是样例居然能过......
那么这道题经过信息整理可以发现,我们需要两个数组:一个存方格上障碍物的位置(map[i][j]数组表示),一个存机器人可以走的格点的位置(map_[i][j])数组表示,则根据样例我们可以整理成这张图:
其中黑色为数组map[i][j]的下标,绿色为map_[i][j]的下标。根据题目可以发现,机器人本身也有宽度,所以边界和障碍物的四周,不能走,那么机器人可以到达的地方就是图中蓝色框内的绿色格点。
代码如下:
// 题目:https://www.luogu.com.cn/problem/P1126
// (东E,南S,西W,北N)
// 上=0
// 左=2 右=3
// 下=1
// 注:上 = 北,下 = 南,左 = 西,右 = 东
#include
#include
#include
using namespace std;
const int size = 50 + 5;
bool map[size][size], map_[size][size];
// map 代表的是当前的格子有没有障碍,注意,这是格子,不是线段
// map2 代表的是当前的两点交叉的线段的点可不可走,注意,机器人是在线段上走,而不是在格子上走
// 这里的线段是有(n + 1) * (m + 1)条,所以我的数组下标是从0~n,0~m,注意,我的map数组下标是从1开始的
int number1[4][4] = {{0, 2, 1, 1}, {2, 0, 1, 1}, {1, 1, 0, 2}, {1, 1, 2, 0}}; // number1[i][j] 表示从方向i到方向j所需的步数,当然,在我这个程序里是没有用到的
int number2[2][4] = {{2, 3, 1, 0}, {3, 2, 0, 1}}; // number2[i][j] i = 0时表示是左转,i = 1时表示是右转,j表示当前方向向方向i转动一步后的方向
int number3[4][2] = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}}; // number3[i][j] 表示向当前方向前进1步的xy坐标的变化,j = 0代表这是x坐标的变化,j = 1代表这是y坐标的变化
bool vis[size][size][5]; //vis[i][j][k] 代表当前的两个状态有没有被经历过,ij代表的是当前的坐标,k代表当前的方向
struct node{
int x, y, z, step;
// xy分别代表当前的位置
// z代表当前的方向,详细见第2、3、4行代码
// step代表当前所走的步数
node() {
}
node(const int &x_, const int &y_, const int &z_, const int &step_) {
x = x_; y = y_; z = z_; step = step_;
}
// 构造函数
void print() { // 这是调试用的
printf("%d %d %d %d\n", x, y, z, step);
}
};
/*
参数作用:
1.ch 代表当前方向的所属的字母,详见第1行代码
函数作用:
将当前的方向的所属的字母转化为数字,详细见第2、3、4行代码
*/
int count(const char &ch) {
if (ch == 'N') return 0;
if (ch == 'S') return 1;
if (ch == 'W') return 2;
return 3;
// 详细见第2、3、4行代码
}
int main() {
freopen("cpp.in", "r", stdin);
freopen("cpp.out", "w", stdout);
int n, m, sx, sy, ex, ey;
char ch;
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; ++i)
for (int j = 1; j <= m; ++j)
cin >> map[i][j];
scanf("%d%d%d%d %c", &sx, &sy, &ex, &ey, &ch); // 空格要打
// 以上是读入部分
// ++sx, ++sy, ++ex, ++ey; 这里是我之前写错的地方,其实是不用加1,因为我们的map_的范围是从0~n, 0~m的
// 这里是将坐标从格子变为线段
for (int i = 1; i <= n; ++i) {
for (int j = 1; j <= m; ++j)
if (map[i][j]) // 如果当前格子上有障碍物,那么我们要将map_的相关位置也赋值为不可走
map_[i][j] = map_[i][j - 1] = map_[i - 1][j] = map_[i - 1][j - 1] = 1; // 这条式子是用来标记map_中不可走的点,是通过观察得来的,具体的可以自己观察一下
// map_[i][j] = map_[i][j + 1] = map_[i + 1][j] = map_[i + 1][j + 1] = 1; // 上次的代码是写了这条式子,因为我之前吧数组下标看成了从1开始
}
// 调试代码
/*
for (int i = 0; i <= n; ++i) {
for (int j = 0; j <= m; ++j) {
cout << map_[i][j];
}
cout << endl;
}
*/
// 调试代码
queue q;
q.push(node(sx, sy, count(ch), 0));
vis[sx][sy][count(ch)] = true; // 将初始状态加入队列并将初始状态赋值为走过
while (!q.empty()) {
node now = q.front();
q.pop();
// now.print(); // 调试代码
if (now.x == ex && now.y == ey) { // 如果到了终点,那直接可以输出步数,然后return;
printf("%d", now.step);
return 0;
}
++now.step; // 这里的步数++是因为我们现在要执行1秒的命令
// 左转
now.z = number2[0][now.z]; // 先转一下
if (!vis[now.x][now.y][now.z]) { // 如果当前状态没经历过
q.push(now); // 先加入队列
vis[now.x][now.y][now.z] = true; // 在将当前状态赋值为经历过
}
now.z = number2[1][now.z]; // 然后转回来
// 右转
now.z = number2[1][now.z]; // 先转一下
if (!vis[now.x][now.y][now.z]) { // 如果当前状态没经历过
q.push(now); // 先加入队列
vis[now.x][now.y][now.z] = true; // 在将当前状态赋值为经历过
}
now.z = number2[0][now.z]; // 然后转回来
// 前进
// 这里我采用的策略是一步一步走,先走一步,加入queue。然后再走一步(现在一共走了2步),加入queue。然后再走一步(现在一共走了3步),加入queue。
for (int i = 1; i <= 3; ++i) {
now.x += number3[now.z][0], now.y += number3[now.z][1];
if (now.x < 1 || now.y < 1 || now.x >= n || now.y >= m/*这个判断有没有出界的条件是经观察所发现的,具体的可以自己观察*/ || map_[now.x][now.y] /*之前写代码的时候忘记加这句话了,这里加这个条件是因为如果我们的机器人走到一半撞墙了,接下来如果再前进,也只会在走的途中撞墙*/) break; // 中途如果已经走出了储藏室或者撞墙了,那代表继续走下去只会更加超限,所以直接return;
if (vis[now.x][now.y][now.z]) continue; // 如果当前状态已经遍历过,那是要continue,注意,当前的状态走过不代表之后的状态走过
// 如果没有出界,没有撞墙,没有经历过
q.push(now); // 那就将当前状态加入队列
vis[now.x][now.y][now.z] = true; // 并将当前的状态赋值为经历过
}
}
printf("-1");
return 0;
}