【p1126-机器人搬重物】解题记录

原题点 这里

这是一道非常经典的迷宫问题,同时考察了编程技巧和宽搜。

题中有许多需要注意的地方:

  • 一个机器人占据四个格子,给出的起终点坐标都是其中左上角格子的坐标。
  • 可能机器人的初始位置的 4 个格子中就有障碍物,因此需要特判起点是否合法。这是最后一组数据的要点。
  • 机器人能前进两格的前提是它能前进一格。前进 3 格的前提是它能前进两格,因此不能仅判断终点位置是否有障碍,而是沿途经过的所有位置是否有障碍。

而本题编程上的技巧也有很多:

  • 可以通过宏在任何地方创建内联函数,合并操作,简化代码。
  • 对于当前 BFS 树的节点表示,除了需要记录行列位置外,还要保存方向和已经操作的步数。
  • 可以用 0、1、2、3 来给方向顺时针编码(我把 N 设为 0,E 为 1,S 为 2,W 为 3),这样左转时将当前方向 -1+4 再 mod 4 即可,右转 +1 再 mod 4 即可。加 4 和 mod 4 是为了保证结果仍在 [0,3] 的范围内。
  • 实际上对于前进操作,只编写前进一步的函数即可,前进 3 步等价于前进 3 个一步。而且前进一步后会发现机器人仍有两格还在原来的位置,肯定没有障碍,因此仅判断机器人走出的两个新格子是否有障碍即可。
  • 对于转向操作,肯定是能够进行的,直接入队即可。而前进操作则可能失败,如走出棋盘或遇到障碍。本人的处理方法是如果新状态不可行则将其前进方向设为 -1,入队时忽略方向为 -1 的节点。
#include <cstdio>
#include <queue>
#include <cstdlib>
#include <cctype>
using namespace std;

const int maxn= 55;
struct Unit{
    int val;
} map[maxn][maxn];

// row = 行,col = column = 列。
// ori = origin = 起点
// tar = target = 终点
// dir = direction = 方向
int map_row, map_col, ori_row, ori_col, ori_dir, tar_row, tar_col;

struct Stat{
    int row, col, dir, cnt;
};

bool inq[maxn][maxn][4];

// 返回前进一步后的状态,失败时返回值的 dir 为 -1 。
Stat go(Stat s){ 
    if(s.dir==-1) return s;  // 已经失败的状态直接结束。
    int r1, c1, r2, c2; // 新扩展的两个格子坐标。
    #define not_in_map() (r1<1 || r1>map_row || c1<1 || c1>map_col ||\
                           r2<1 || r2>map_row || c2<1 || c2>map_col)
    #define have_stone() (map[r1][c1].val||map[r2][c2].val)
    // 开始判断新格子的坐标。
    if(s.dir==0){
        --s.row;
        r1= s.row; c1=s.col; r2= s.row; c2= s.col+1;    
    }
    else if(s.dir==1){
        ++s.col;
        r1= s.row; c1= s.col+1; r2= s.row+1; c2= s.col+1;       
    }
    else if(s.dir==2){
        ++s.row;
        r1= s.row+1; c1= s.col; r2= s.row+1; c2= s.col+1;
    }
    else{
        --s.col;
        r1= s.row; c1= s.col; r2= s.row+1; c2= s.col;
    }
    // 如果新格子有障碍或机器人走出地图,则失败。
    if(not_in_map()||have_stone()){ s.dir= -1; }
    return s;
}

queue<Stat> q;

void bfs(){
    // 宏可以随处定义,非常方便,但不建议在工程中使用。
    // 另外给宏的参数最好只有一个变量名,否则容易引起各种奇怪的错误。
    #define is_inq(s) inq[s.row][s.col][s.dir]
    #define push(s) q.push(s); inq[s.row][s.col][s.dir]=true; 
    #define _left(s) (Stat){s.row, s.col, (s.dir+3)%4, s.cnt}
    #define _right(s) (Stat){s.row, s.col, (s.dir+1)%4, s.cnt}
    #define pop() q.front(); q.pop(); 
    Stat ori_stat= {ori_row, ori_col, ori_dir, -1};
    // 特判起点是否有障碍,有则直接结束。
    if(map[ori_stat.row][ori_stat.col].val
     ||map[ori_stat.row][ori_stat.col+1].val
     ||map[ori_stat.row+1][ori_stat.col].val
     ||map[ori_stat.row+1][ori_stat.col+1].val){
         printf("-1\n"); exit(0);
     }

    push(ori_stat);    
    while(q.empty()==false){
        Stat front= pop(); ++front.cnt;
        if(front.row==tar_row && front.col==tar_col){
            printf("%d\n", front.cnt); exit(0); // 到达终点直接结束。
        }

        Stat left= _left(front), right= _right(front);
        // 转向的结果只要不重复就入队。
        if(!is_inq(left)) push(left);
        if(!is_inq(right)) push(right);

        Stat forward[3];
        // 将 3 步转化为依次走 1 步。
        forward[0]= go(front);
        forward[1]= go(forward[0]);
        forward[2]= go(forward[1]);
        for(int i=0; i<3; ++i){
            Stat now= forward[i];
            // 前进的结果除了判重还要检查是否成功。
            if(now.dir==-1 || is_inq(now)) continue; 
            push(now);
        }
    }
    printf("-1\n");    
}

int main(){
    scanf("%d%d", &map_row, &map_col);
    for(int i=1; i<=map_row; ++i){
        for(int j=1; j<=map_col; ++j){
            scanf("%d", &map[i][j].val);
        }
    }
    char dir_ch= ' ';
    scanf("%d%d%d%d", &ori_row, &ori_col, &tar_row, &tar_col);
    // 尽量不要用 scanf 读取单个字符。
    while(isspace(dir_ch=getchar()));
    // 将方向转化为数字。
    if(dir_ch=='N') ori_dir= 0;
    else if(dir_ch=='E') ori_dir= 1;
    else if(dir_ch=='S') ori_dir= 2;
    else ori_dir= 3;
/*    
    printf("%d %d %d %d %d %d %d\n", map_row, map_col, ori_row, ori_col, ori_dir, tar_row, tar_col);
    for(int i=1; i<=map_row; ++i){
        for(int j=1; j<=map_col; ++j){
            printf("%d ", map[i][j].val);
        }
        putchar('\n');
    }
*/   
    bfs();    
    return 0;   
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值