机器人搬重物--复杂的BFS

雒仁韬

2018-10-28 19:59:13

Solution

这道题,毫无疑问,就是广搜。但是它要注意的细节非常多。所以这道题对逻辑和代码能力的的考察很高 首先,这道题需要注意,障碍物是在格子上,而机器人是在格点上走。某人~~就是我~~自信地写完了代码后发现题读错了。关键最讨厌的是样例居然能过...... 那么这道题经过信息整理可以发现,我们需要两个数组:一个存方格上障碍物的位置(sd[i][j]数组表示),一个存机器人可以走的格点的位置(a[i][j])数组表示,则根据样例我们可以整理成这张图: ![样例图示](https://cdn.luogu.com.cn/upload/pic/40415.png) 其中黑色为数组sd[i][j]的下标,绿色为a[i][j]的下标。根据题目可以发现,机器人本身也有宽度,所以边界和障碍物的四周,不能走,那么机器人可以到达的地方就是图中蓝色框内的绿色格点。所以边界条件判断时,n和m各要减1。 然后我们还需要注意的地方是题中~~害人不浅~~的方向处理。于是为了方便存储,我给每个方向都编了一个号:↑为1,↓为2,←为3,→为4。然后转向的时候就更加麻烦了。于是我为了好判断情况,emm......用了好几个数组 ```cpp int fx[5]={0,-1,1,0,0};//fx[i]表方向i(编号)的x的进退情况 int fy[5]={0,0,0,-1,1};//fy[i]表方向i(编号)的y的进退情况 int ft[5]={0,1,4,2,3};//ft[i]表示顺时针排列各个方向的编号(上1 右4 下2 左3) int fft[5]={0,1,3,4,2};//fft[i]表示数字i在ft[]数组中的下标 int abc[5]={0,1,2,1,0};//abc[5]表示转到[顺时针转i次到达的那个方向]的最短次数 ``` 其中ft数组和abc数组比较难理解 先讲abc数组 ![abc数组解释](https://cdn.luogu.com.cn/upload/pic/40416.png) 如图,不难看出当对于不同的i,也就是顺时针转动i次时,对应的最小旋转次数就是abc[i] 而ft数组~~也比较好理解~~ abc图中蓝圈内四个方向各有一个黑色数字代表方向编号,那么我们顺时针遍历一下就是1 4 2 3. 我么还要注意的地方是起点和终点可能重合,需要特判;起点可能就有障碍物,也要特判。 最后要注意的是,对于每一步,你走一个格点,两个格点或三个格点所耗时间都是1,不要搞乱;而每转90°,就要耗一定时间,所以千万要小心。 于是,开始写BFS 用队列存储每一个格点的信息,然后起点入队,每次从队首取出一个元素,根据这个元素旋转,直行,得到一个新的坐标,然后判断由这种方式到达的这个点是否是耗时最短的一种方式(类似于Dijkstra中的dis数组),此处用f[i][j]数组表示,然后队列为空后,输出f[终点的x][终点的y]即可。 ~~丧心病狂的~~代码如下 ```cpp #include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #include<string> #include<queue> #include<cmath> #include<iomanip> using namespace std; int sd[55][55]; int a[55][55];//a为读入的方格地图 int n,m; int x11,y11;//起点 int x2,y2;//终点 int f[55][55];//f为格点地图 int fx[5]={0,-1,1,0,0};//fx[i]表方向i(编号)的x情况 int fy[5]={0,0,0,-1,1};//fy[i]表方向i(编号)的y情况 int ft[5]={0,1,4,2,3};//ft[i]表示顺时针排列各个方向的编号(上1 右4 下2 左3) int fft[5]={0,1,3,4,2};//fft[i]表示数字i在ft[]数组中的下标 int abc[5]={0,1,2,1,0};//abc[5]表示转到[顺时针转i次到达的那个方向]的最短次数 struct node { int x,y;//当前点的坐标 int t;//1=>N 2=>S 3=>W 4=>E 方向编号 int time;//从起点到当前点的最短时间 }; queue<node> q;//队列q string ch;//读入起点的方向 int cto;//起点的方向 void fxto() { switch(ch[0]) { case 'N': cto=1;break; case 'S': cto=2;break; case 'W': cto=3;break; case 'E': cto=4;break; } return; } void change() { for(int i=1;i<=n;++i) { for(int j=1;j<=m;++j) { if(sd[i][j]==1)//如果当前格为障碍物,则它的四个顶点都不能走 { a[i-1][j]=1; a[i][j-1]=1; a[i-1][j-1]=1; a[i][j]=1; } } } } int main() { cin>>n>>m; for(int i=1;i<=n;++i) { for(int j=1;j<=m;++j) { scanf("%d",&sd[i][j]); } } cin>>x11>>y11>>x2>>y2; cin>>ch; fxto();//判断ch代表的方向 change();//把方格地图转化为机器人可以走的格点地图 node first;//起点 first.x=x11; first.y=y11; first.t=cto; first.time=0; q.push(first);//起点入队 node u,d; while(!q.empty()) { u=q.front(); q.pop(); for(int i=1;i<=4;++i) { int zhuan=abc[i];//[顺时针转i下的那个方向]的最短旋转次数 //求出旋转完了以后方向的编号fangx(为了方便讨论,全部当做顺时针旋转) int fangx=fft[u.t]+i;//此时fangx为下标 if(fangx==5) fangx=1; if(fangx==6) fangx=2; if(fangx==7) fangx=3; if(fangx==8) fangx=4; fangx=ft[fangx];//此时fangx为方向编号 //此时fangx存的是由当前点顺时针转了i次后到达的方向的编号 for(int j=1;j<=3;++j)//走1~3步 { int lsx=u.x+fx[fangx]*j;//计算按当前旋转方向走j步的坐标 int lsy=u.y+fy[fangx]*j; if(lsx>=n || lsx<=0 || lsy>=m || lsy<=0 || (lsx==x11&&lsy==y11) || a[lsx][lsy]==1) { //判断边界和障碍物 (特判:是否为起点) break; } if((u.time+zhuan+1<f[u.x+fx[fangx]*j][u.y+fy[fangx]*j] || f[u.x+fx[fangx]*j][u.y+fy[fangx]*j]==0) && a[u.x+fx[fangx]*j][u.y+fy[fangx]*j]==0) {//如果当前点可以刷新距离,就入队 d.x=u.x+fx[fangx]*j; d.y=u.y+fy[fangx]*j; d.t=fangx; d.time=u.time+zhuan+1; f[u.x+fx[fangx]*j][u.y+fy[fangx]*j]=d.time; q.push(d); } } } } if(f[x2][y2]==0 && (x2!=x11 || y2!=y11))//如果为0,代表不能走到 { cout<<"-1"<<endl; } else//否则输出终点的距离 cout<<f[x2][y2]<<endl; return 0; } ```