题意 : 机器人要从一个m * n 网格的左上角(1,1) 走到右下角(m, n)。网格中的一些格子是空地(用0表示),其他格子是障碍(用1表示)。机器人每次可以往4个方向走一格,但不能连续地穿越k(0≤k≤20)个障碍,求最短路长度。起点和终点保证是空地。
分析 : 很明显的BFS最短路,但是这里有坑呀!如果只是单纯使用二维数组去标记是否已经访问过改点是错误的做法,走到该点的机器人因为有穿越障碍物的步数限制,所以可能有些 略绕但是行得通的路 就会被二维数组这种标记方式把路给封死,比如下面这个例子,假设BFS机器人的走位是先右后左,并且k = 3,如果采用二维标记则会出错
0 1 1 1 1
0 1 1 1 0
为什么会有这种情况呢?因为在普通BFS中能走的都是通路,如果当前点再去走已经被标记的点必然不是最优,而现在对于机器人而言,又有一个可跨越障碍的步数这个限制,那么走到某一个点当然是机器人还能跨越障碍物的步数越多越好,也就是说对于同一个点只要没有走已经走过的点必然不是最优这种条件的话,就要慎重考虑使用普通的BFS解法了!现在回到这个题,其实解决的办法就是给vis再增加一个维度,表示当前机器人是跨越了多少个障碍物来到这里的这个状态即可。不过实际上根据刚刚的分析,我们可以做个小小的优化,就是如果当前机器人到达这个点还能跨越障碍物的步数少于之前已经来过这里的机器人的话,那必然不是最优,无需入队!
#include<bits/stdc++.h> using namespace std; int n, m, k, ans; struct robot { robot(int r, int c, int C, int s):row(r), col(c), cross(C), step(s){}; int row, col, cross, step; }; int dr[] = {1, -1, 0, 0}; int dc[] = {0, 0, 1, -1}; bool vis[25][25][25], G[25][25]; int Chance[25][25];//记录当前机器人还剩的跨越障碍物的机会 bool bound(int r, int c){ return (r>n || c>m || r<1 || c<1); } inline void BFS() { queue<robot> q; memset(vis, false, sizeof(vis)); memset(Chance, 0x3f, sizeof(Chance)); q.push(robot(1,1,0,0)); vis[1][1][0] = true; while(!q.empty()){ robot tmp = q.front(); q.pop(); if(tmp.row==n && tmp.col==m){ ans = tmp.step; break; } for(int i=0; i<4; i++){ int row = tmp.row + dr[i]; int col = tmp.col + dc[i]; int C = tmp.cross; if(G[row][col] == false) C+=1; else C = 0; if( !bound(row, col) && C <= k && !vis[row][col][C] && C < Chance[row][col]){//当前机器人到达这个点还能跨越更多的障碍物 vis[row][col][C] = true; Chance[row][col] = C; q.push(robot(row, col, C, tmp.step+1)); } } } } int main(void) { // freopen("in.txt", "r", stdin); // freopen("out.txt", "w", stdout); int nCase; scanf("%d", &nCase); while(nCase--){ scanf("%d %d %d", &n, &m, &k); ans = -1; memset(G, false, sizeof(G)); for(int i=1; i<=n; i++){ for(int j=1; j<=m; j++){ int BOOL; scanf("%d", &BOOL); if(!BOOL) G[i][j] = true; } } BFS(); printf("%d ", ans); } return 0; }
瞎 : 已经碰见过两次这种可以跨障碍物的BFS最短路题了,要时刻清楚vis标记的是状态,根据状态有无更优来舍取是否入队操作。