洛谷1126机器人搬重物

题目

题目描述

扣我传送
机器人移动学会(RMI)现在正尝试用机器人搬运物品。机器人的形状是一个直径 1.6 1.6 1.6米的球。在试验阶段,机器人被用于在一个储藏室中搬运货物。储藏室是一个 N × M N \times M N×M 的网格,有些格子为不可移动的障碍。机器人的中心总是在格点上,当然,机器人必须在最短的时间内把物品搬运到指定的地方。机器人接受的指令有:向前移动 1 1 1步(Creep);向前移动2步(Walk);向前移动 3 3 3 步(Run);向左转(Left);向右转(Right)。每个指令所需要的时间为 1 1 1 秒。请你计算一下机器人完成任务所需的最少时间。

输入格式

第一行为两个正整数 N , M ( N , M ≤ 50 ) N,M(N,M \le 50) N,M(N,M50),下面 N N N行是储藏室的构造, 0 0 0表示无障碍, 1 1 1表示有障碍,数字之间用一个空格隔开。接着一行有 4 4 4个整数和 1 1 1个大写字母,分别为起始点和目标点左上角网格的行与列,起始时的面对方向(东 E E E,南 S S S,西 W W W,北 N N N),数与数,数与字母之间均用一个空格隔开。终点的面向方向是任意的。

输出格式

一个整数,表示机器人完成任务所需的最少时间。如果无法到达,输出 − 1 -1 1

在这里插入图片描述

样例 #1

样例输入 #1

9 10
0 0 0 0 0 0 1 0 0 0
0 0 0 0 0 0 0 0 1 0
0 0 0 1 0 0 0 0 0 0
0 0 1 0 0 0 0 0 0 0
0 0 0 0 0 0 1 0 0 0
0 0 0 0 0 1 0 0 0 0
0 0 0 1 1 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0
1 0 0 0 0 0 0 0 1 0
7 2 2 7 S

样例输出 #1

12

代码以及思路

一道BFS的题,但是要注意的东西非常非常多。

要注意的点:

首先注意到机器人是直径1.6m的球,所以当机器人在格点时周围四个方块都得是空的。

其次,还是因为有体积,所以机器人也不能在最外围行走。

因此,我们用一个obtacle数组来存储地图。下标从1开始。

思路

不管如何,看完题目先搓一格Point类出来:


struct Point {
    int x, y;
    int time, dire;
    Point(int _x, int _y, int _t, int _d) : x(_x), y(_y), time(_t), dire(_d) { ; }
    // 仅需判断坐标即可
    bool operator==(const Point b) const{
        return (this->x == b.x && this->y == b.y);
    }
};

机器人一步可以进行5格操作,分别是:

  • 前进1格
  • 前进2格
  • 前进3格
  • 向左转
  • 向右转

机器人移动

很明显,前3个和后2个可以分成不同类别,我们先不考虑机器人的转向问题,就考虑机器人怎么走。机器人可以在一步内往上下左右走1~3格,这样当机器人在条件允许的情况下一个格子最多能衍生出12个走法,我按照上下左右,走1格到走3格的顺序写出如下数组:

// 上下左右上下左右....
int dx[12] = {0, 0, -1, 1, 0, 0, -2, 2, 0, 0, -3, 3};
int dy[12] = {-1, 1, 0, 0, -2, 2, 0, 0, -3, 3, 0, 0};

这样我们在之后BFS的时候只要一个循环就能搞定了


机器人转向

机器人一步只能转90度。只需要对比目前的朝向以及移动需求朝向就可以算出机器人花在转向上的时间了。

一种表示的方法:

// 上下定义为-1, 1; 左右定义-2, 2;
int direction[12] = {-1, 1, -2, 2, -1, 1, -2, 2, -1, 1, -2, 2};

// 计算转弯的耗时
int turn_times(int origin, int dest) {
    if (origin + dest == 0) return 2;
    if (origin == dest) return 0;
    return 1;
}

在上面的代码里,direction数组分别对应上面的dxdy。


BFS

在写BFS前先把他需要的东西准备好,比如用来标记的数组之类的,在这里我用done数组来标记是否走过。

很容易能想出一个基本框架:

int bfs(Point start_p, Point end_p) {
    queue<Point> points;
    points.push(start_p);
    while (!points.empty()) {
        Point now_p = points.front();
        points.pop();
        if (now_p == end_p) return now_p.time;
        
        /*
        扩展新的点。。。
        */
    }
    return -1
}

如何扩展新的点就是这道题最关键的地方。我们在上面有12种走法,因此在这里我们for循环也来12次

for (int i = 0; i < 12; i++) {
	int new_x = now_p.x + dx[i];
	int new_y = now_p.y + dy[i];
	int new_time = turn_times(now_p.dire, direction[i]) + 1 + now_p.time;

	/*
	判断接下来走到的点是否能走
	*/
}

因为我们判断的条件比较复杂,还能继续拆分成多个条件,我其中一个把判断机器人能否站在这个点的功能封装到外边去,命名为judge

judge函数

先理清这个函数要干的活,简化为如下:

  • 判断这个点在不在地图周围一圈或之外(确保下一个判断安全)
  • 判断这个点周围四格是否有障碍物

这个函数只是判断机器人能否站在某个点上

很容易写出以下的代码:

// 能否到达这个点
bool judge(int x, int y) {
    if (x < 1 || y < 1 || x >= xmax || y >= ymax) return false;
    // 有障碍? 这里要转到obtacle的坐标
    int _dx[4] = {0, 1, 0, 1};
    int _dy[4] = {0, 0, 1, 1};
    for (int i = 0; i < 4; i++) {
        int new_x = x + _dx[i];
        int new_y = y + _dy[i];
        if (obtacle[new_y][new_x] == 1) return false;
    }
    return true;
}

注意下机器人走的是格点,与障碍不一样。


机器人能到这个点吗?

机器人从某个点出发有12种走法,但是即使我们判断了12个点种某些点机器人能站立在上面,我们也不能确定机器人就一定能一步到达这些点。比如以下情况:

在这里插入图片描述

虽然理论上目标点是可以站立的,但是路径上存在不可站立的点,所以这个点也是不可到达的。

所以机器人想要一步到达某个点,需要路径上的点都能走通。我们可以借此来简化条件,即:

当机器人在某个方向走n格不可达时,那么这个方向上n+1格也是不可达的。

我们在for循环外新建一个bool数组(我们命名为can_go)用来存储第i种走法的点是否时可达的,先把这个数组全部置为true,走一格的情况时肯定要判断是否可站立的,2格3格就可以用这个数组进行判断了。加上这个点是否被走过的判断后代码如下:

bool can_go[16];
fill(can_go, can_go + 16, true);
for (int i = 0; i < 12; i++) {
    int new_x = now_p.x + dx[i];
    int new_y = now_p.y + dy[i];
    int new_time = turn_times(now_p.dire, direction[i]) + 1 + now_p.time;

    if (!can_go[i] || !judge(new_x, new_y)) can_go[i + 4] = false;
    else if (!done[new_y][new_x]){
        done[new_y][new_x] = true;
        points.push(Point(new_x, new_y, new_time, direction[i]));
    }
}

这里can_go开16偷个懒避免溢出


仍然是WA

上面的代码还是有错的地方。因为这个花费的时间与传统的步数还是有很大的不同。从不同的点到达同一个点花费的时间很可能不一样。比如一下情况:

在这里插入图片描述

从左边走只需要4步,从上面走需要5步。

因此需要记录到达每个格点的最短时间,这里我开了一个mintime数组用来记录。这样在判断是否要加入一个点的时候不仅仅是看这个点是否被遍历过,还要看这个点的时间是否比之前的遍历要小。

同时在while循环的时候也要判断下是否这个点的时间是最小的,如果不是就continue。

代码如下:

int bfs(Point start_p, Point end_p) {
    queue<Point> points;
    points.push(start_p);
    while (!points.empty()) {
        Point now_p = points.front();
        points.pop();
        if (mintime[now_p.y][now_p.x] != now_p.time) continue;

        if (now_p == end_p) return now_p.time;

        bool can_go[16];
        fill(can_go, can_go + 16, true);
        for (int i = 0; i < 12; i++) {
            int new_x = now_p.x + dx[i];
            int new_y = now_p.y + dy[i];
            int new_time = turn_times(now_p.dire, direction[i]) + 1 + now_p.time;

            if (!can_go[i] || !judge(new_x, new_y)) can_go[i + 4] = false;
            else if (!done[new_y][new_x] || new_time < mintime[new_y][new_x]){
                mintime[new_y][new_x] = new_time;
                done[new_y][new_x] = true;
                points.push(Point(new_x, new_y, new_time, direction[i]));
            }
        }
    }
    return -1;
}

代码

#include<bits/stdc++.h>
using namespace std;

int obtacle[55][55], mintime[55][55];
bool done[55][55];

// 上下左右上下左右....
int dx[12] = {0, 0, -1, 1, 0, 0, -2, 2, 0, 0, -3, 3};
int dy[12] = {-1, 1, 0, 0, -2, 2, 0, 0, -3, 3, 0, 0};

// 上下定义为-1, 1; 左右定义-2, 2;
int direction[12] = {-1, 1, -2, 2, -1, 1, -2, 2, -1, 1, -2, 2};

int ymax, xmax;

struct Point {
    int x, y;
    int time, dire;
    Point(int _x, int _y, int _t, int _d) : x(_x), y(_y), time(_t), dire(_d) { ; }
    // 仅需判断坐标即可
    bool operator==(const Point b) const{
        return (this->x == b.x && this->y == b.y);
    }
};

// 能否走通这个点
bool judge(int x, int y) {
    if (x < 1 || y < 1 || x >= xmax || y >= ymax) return false;
    // 有障碍? 这里要转到obtacle的坐标
    int _dx[4] = {0, 1, 0, 1};
    int _dy[4] = {0, 0, 1, 1};
    for (int i = 0; i < 4; i++) {
        int new_x = x + _dx[i];
        int new_y = y + _dy[i];
        if (obtacle[new_y][new_x] == 1) return false;
    }
    return true;
}

// 计算转弯的耗时
int turn_times(int origin, int dest) {
    if (origin + dest == 0) return 2;
    if (origin == dest) return 0;
    return 1;
}

int translate_direction(char ch) {
    if (ch == 'N') return -1;
    if (ch == 'S') return 1;
    if (ch == 'W') return -2;
    return 2;
}

int bfs(Point start_p, Point end_p) {
    queue<Point> points;
    points.push(start_p);
    while (!points.empty()) {
        Point now_p = points.front();
        points.pop();
        if (mintime[now_p.y][now_p.x] != now_p.time) continue;

        if (now_p == end_p) return now_p.time;

        bool can_go[16];
        fill(can_go, can_go + 16, true);
        for (int i = 0; i < 12; i++) {
            int new_x = now_p.x + dx[i];
            int new_y = now_p.y + dy[i];
            int new_time = turn_times(now_p.dire, direction[i]) + 1 + now_p.time;

            if (!can_go[i] || !judge(new_x, new_y)) can_go[i + 4] = false;
            else if (!done[new_y][new_x] || new_time < mintime[new_y][new_x]){
                mintime[new_y][new_x] = new_time;
                done[new_y][new_x] = true;
                points.push(Point(new_x, new_y, new_time, direction[i]));
            }
        }
    }
    return -1;
}

int main() {
    cin >> ymax >> xmax;
    for (int yi = 1; yi <= ymax; yi++) {
        for (int xi = 1; xi <= xmax; xi++) {
            cin >> obtacle[yi][xi];
        }
    }
    int x1, y1, x2, y2;
    char ch;
    cin >> y1 >> x1 >> y2 >> x2 >> ch;
    done[y1][x1] = true;
    mintime[y1][x1] = 0;
    cout << bfs(Point(x1, y1, 0, translate_direction(ch)), Point(x2, y2, 0, 0)) << endl;
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值