原题:
机器人搬重物
题目描述
机器人移动学会(RMI
)现在正尝试用机器人搬运物品。机器人的形状是一个直径 1.6 米的球。在试验阶段,机器人被用于在一个储藏室中搬运货物。储藏室是一个 N×M 的网格,有些格子为不可移动的障碍。机器人的中心总是在格点上,当然,机器人必须在最短的时间内把物品搬运到指定的地方。机器人接受的指令有:向前移动 1 步(Creep
);向前移动 2 步(Walk
);向前移动 3 步(Run
);向左转(Left
);向右转(Right
)。每个指令所需要的时间为 1 秒。请你计算一下机器人完成任务所需的最少时间。
输入格式
第一行为两个正整数 N,M (N,M ≤ 50),下面 N 行是储藏室的构造,0 表示无障碍,1 表示有障碍,数字之间用一个空格隔开。接着一行有 4 个整数和 1 个大写字母,分别为起始点和目标点左上角网格的行与列,起始时的面对方向(东 E,南 S,西 W,北 N),数与数,数与字母之间均用一个空格隔开。终点的面向方向是任意的。
输出格式
一个整数,表示机器人完成任务所需的最少时间。如果无法到达,输出 −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 #include<stdio.h> 2 #include<iostream> 3 #include<queue> 4 using namespace std; 5 struct node{ int x,y,s,z; }now; 6 int n,m,sx,sy,ex,ey,ans,state,g[55][55],mark[55][55][5]; 7 //sx,sy起点坐标(start) ,ex,ey终点坐标(end) 8 //state标记最开始方向,g储存地图,mark标记 9 //mark的三个下标分别确定行,列,方向 10 int d[4]={1,-1,2,-2},d2[3]={1,2,3}; 11 //d用于转换方向,d2用于移动 12 char ch; 13 queue<node> q; 14 bool check(int x,int y) 15 { 16 if(x<1||x>=n||y<1||y>=m) return 0; 17 //判断是否出界 18 if(g[x][y]||g[x][y+1]||g[x+1][y]||g[x+1][y+1]) return 0; 19 //判断所在地是否有障碍物 20 return 1; 21 } 22 void bfs(int x,int y,int state) 23 { 24 for(int i=1;i<=n;i++) 25 for(int j=1;j<=m;j++) 26 for(int k=0;k<5;k++) mark[i][j][k]=0x7fffffff; 27 //初始化为最大值,方便下面更新最小值 28 mark[x][y][state+2]=0,q.push({x,y,0,state}); 29 while(!q.empty()) 30 { 31 now=q.front(); q.pop();//取出队首 32 int x=now.x,y=now.y,s=now.s,z=now.z; 33 for(int i=0;i<4;i++) 34 {//依次判断四个方向移动是否可行 35 int tz,flag=0; 36 if(d[i]==z) tz=z,flag=1; 37 else if(d[i]+z) tz=d[i]; 38 else tz=d[i],flag=-1; 39 //flag判断转换方向的特殊情况,1表示不动(0s),-1表示向后转(2s) 40 for(int j=1;j<=3;j++) 41 { 42 int k,tx=x,ty=y,ts=s+2;//一般转向+移动共需2s 43 if(flag==1) ts--;//不转向共需1s 44 else if(flag==-1) ts++;//不转向共需2s 45 if(tz==2)//东 46 { 47 for(k=1;k<=j;k++) 48 { 49 ty++; 50 if(!check(tx,ty)) break; 51 //移动过程中每一格都需要判断 52 } 53 if(k<=j) continue;//如果中途退出过表示不能移动 54 }//以下代码复制略作修改即可 55 else if(tz==-2)//西 56 { 57 for(k=1;k<=j;k++) 58 { 59 ty--; 60 if(!check(tx,ty)) break; 61 } 62 if(k<=j) continue; 63 } 64 else if(tz==1)//南 65 { 66 for(k=1;k<=j;k++) 67 { 68 tx++; 69 if(!check(tx,ty)) break; 70 } 71 if(k<=j) continue; 72 } 73 else if(tz==-1)//北 74 { 75 for(k=1;k<=j;k++) 76 { 77 tx--; 78 if(!check(tx,ty)) break; 79 } 80 if(k<=j) continue; 81 } 82 if(mark[tx][ty][tz+2]>ts) 83 { 84 q.push({tx,ty,ts,tz}); 85 mark[tx][ty][tz+2]=ts; 86 } 87 } 88 } 89 } 90 return; 91 } 92 int main() 93 { 94 scanf("%d %d",&n,&m); 95 for(int i=1;i<=n;i++) 96 for(int j=1;j<=m;j++) scanf("%d",&g[i][j]); 97 scanf("%d %d %d %d %c",&sx,&sy,&ex,&ey,&ch); 98 switch(ch) 99 { 100 case 'S':state=1;break; 101 case 'E':state=2;break; 102 case 'N':state=-1;break; 103 case 'W':state=-2;break; 104 }//东西南北四个方向 105 bfs(sx,sy,state); 106 int A=min(mark[ex][ey][0],mark[ex][ey][1]); 107 int B=min(mark[ex][ey][3],mark[ex][ey][4]); 108 ans=min(A,B);//寻找在终点时的最小步数 109 if(ans!=0x7fffffff) printf("%d ",ans); 110 else printf("-1 ");//若没有被更新过代表无解 111 return 0; 112 }
分块解析:
BFS基本思路:把所有可行的、未标记过的点依次入队(当然了首先入队的是起点),依次取出后按所有可能性拓展,若没有可以再拓展的点代表搜索完毕。
注意点1:一个机器人占据四个格子,所以代码中都使用左上角的坐标来储存。
注意点2:题目中描述的允许操作是
①向前移动 1 步(Creep
)
②向前移动 2 步(Walk
)
③向前移动 3 步(Run
)
④向左转(Left
)
⑤向右转(Right
)
在这里,允许的操作是左右旋转,所以我们需要宏观的东南西北来判断机器人的旋转方向。
不妨使用2,-2,1,-1分别代表东、西、南、北,只要依次让机器人尝试这些方向,如果当前尝试的方向与原来的朝向的和为0,代表向后转;同样的如果相同即没有旋转,其他情况都为向左或右旋转。
旋转了就必须移动(这不是废话嘛......不然你转他干什么),所以可以将旋转与移动合并为一次操作计时。
注意在mark数组记录的时候要记录2个数据:坐标以及朝向。只有在同一地点、同一种朝向的情况下,如果当前方案比之前mark标记的最优方案更优才能更新。
注意点3:判断目前搜索到的点是否可行,代码如下
bool check(int x,int y) { if(x<1||x>=n||y<1||y>=m) return 0; if(g[x][y]||g[x][y+1]||g[x+1][y]||g[x+1][y+1]) return 0; return 1; }
要注意一个点!机器人不管是爬还是走还是跑,过去的途中也不可以碰到障碍物!
例如样例中的这个图,机器人(左上角)从网格第四行第一列直接向北跑三格到第一行第一列,若只判断左上角的目标地点是否可行的话,是可以移动的,但实际上这条路线是不被允许的,因为移动过程中会被障碍物挡住(毕竟机器人不是飞过去的),我就是因为这个WA了三次QAQ。所以我的做法是移动过程中碰到的的点依次判断是否可行。具体实现过程见代码第45行至81行。
![](https://img2020.cnblogs.com/blog/1910232/202010/1910232-20201022225836233-129384356.png)