CF2041D Drunken Maze
D. Drunken Maze
You are given a two-dimensional maze with a start and end position. Your task is to find the fastest way to get from the start to the end position. The fastest way is to make the minimum number of steps where one step is going left, right, up, or down. Of course, you cannot walk through walls.
There is, however, a catch: If you make more than three steps in the same direction, you lose balance and fall down. Therefore, it is forbidden to make more than three consecutive steps in the same direction. It is okay to walk three times to the right, then one step to the left, and then again three steps to the right. This has the same effect as taking five steps to the right, but is slower.
Input
The first line contains two numbers 𝑛 and 𝑚, which are the height and width of the maze. This is followed by an ASCII-representation of the maze where # is a wall, . is an empty space, and S and T are the start and end positions.
- \(12≤𝑛×𝑚≤200000\).
- \(3≤𝑛,𝑚≤10000\).
- Characters are only .#𝚂𝚃 and there is exactly one S and one T.
- The outer borders are only # (walls).
Output
The minimum number of steps to reach the end position from the start position or -1 if that is impossible.
问题描述
一道搜索题,给定一个迷宫,起点为 \(S\) ,终点为 \(T\) 。需要从 \(S\) 移动到 \(T\) ,但在同一方向上最多只能连续移动三步。每次移动到第四步时,可以选择改变方向:往回走,或向侧面走一步后再返回,然后继续在原方向上移动。问:在满足上述规则的情况下,从 \(S\) 到 \(T\) 所需的最少步数是多少?如果无法到达 ,返回“\(–1\)”。
思路分析
该题主要是添加了在同一方向上最多只能连续移动三步,然后搜索的思路是不变的,根据之前的思路,可以选一个二维数组,记录到达该点最优值,但这种做法一定是错误或者超时,因为在到达某个点的方向和步数是不同的,假设只访问该点一次,会导致答案错误;访问多次,会执行许多次重复搜索,会导致超时,这时候就需要考虑如何进行剪枝。
根据所给的条件,需要记录下每个点的所有状态信息,进行剪枝,可以看到每个节点需要四个状态信息来确定,横坐标 \(x\) 、纵坐标 \(y\) 、方向 \(t\) 、在方向 \(t\) 下移动的步数 \(c\) ,遍历的状态最多有 \(n\times m\times 4\times 4\) 个,因此不会超时。
考虑使用BFS还是DFS,这里选用的BFS,BFS 能够按照步数从小到大的顺序扩展状态,对于本问题BFS 能确保从起点开始逐步扩展,每次到达新状态时,步数是当前可能的最优解;选择DFS,在每种状态只遍历一次的情况下,可能取不到最优解,具体可以根据以下样例debug理解:
3 6
......
.####.
...T#S
最后附上AC代码:
#include<bits/stdc++.h>
using namespace std;
vector<string>s;//迷宫
vector<vector<vector<vector<int>>>>a;//状态信息
vector<vector<vector<vector<bool>>>>ji;//是否遍历过
int n,m;//大小
int fx=0,fy=0,sx=0,sy=0;//起始终点坐标
int xx[4]={0,0,1,-1};
int yy[4]={1,-1,0,0};//方向数组
const int inf=0x7fffffff;//常量
void solve(){
cin>>n>>m;
//初始化
s.resize(n);
a=vector<vector<vector<vector<int>>>>(n,vector<vector<vector<int>>>(m,vector<vector<int>>(4,vector<int>(4,inf))));
ji=vector<vector<vector<vector<bool>>>>(n,vector<vector<vector<bool>>>(m,vector<vector<bool>>(4,vector<bool>(4))));
//得到S,T的坐标
for(int i=0;i<n;i++){
cin>>s[i];
for(int j=0;j<m;j++){
if(s[i][j]=='S')sx=i,sy=j;
else if(s[i][j]=='T')fx=i,fy=j;
}
}
//标准BFS
ji[sx][sy][0][0]=1;
a[sx][sy][0][0]=0;
queue<array<int,4>>q;
q.push({sx,sy,0,0});
while(!q.empty()){
auto&[x,y,t,c]=q.front();q.pop();
for(int i=0;i<4;i++){
int dx=x+xx[i],dy=y+yy[i];
if(dx>=0&&dy>=0&&dx<n&&dy<m&&s[dx][dy]!='#'){
int cc=(t==i?c+1:1);
//该方向超出三步或已经访问过,直接跳过
if(cc>3||ji[dx][dy][i][cc])continue;
a[dx][dy][i][cc]=a[x][y][t][c]+1;
ji[dx][dy][i][cc]=true;
q.push({dx,dy,i,cc});
}
}
}
int res=inf;
//遍历T的所有16个状态,得到结果。
for(int i=0;i<4;i++)
for(int j=0;j<4;j++){
if(ji[fx][fy][i][j])res=min(res,a[fx][fy][i][j]);
}
cout<<(res==inf?-1:res)<<'\n';
}
signed main() {
ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
int t=1;
// cin>>t;
while(t--)solve();
return 0;
}