题目
这是搜索题中的经典拐弯问题,难点在于怎么标记已经搜索过的点。
对于某一个点来说,从不同路径但是最后从同一个方向到达这个点所需的拐弯数可能是不同的。
例如:
5 5
S.*T.
.....
.....
.....
.....
同样向右走到 (1,2)的两种走法,一种是从右往下,然后再往右,拐了两次弯;而另一种先向下,然后再往右,
实际上只用拐一次弯,所以在搜索过程中需要更新更优解而另一种先向下,然后再往右,实际上只用拐一次弯。
所以在搜索过程中需要更新更优解。
而我们想要求的是是否可以在拐弯尽量少的情况下在两次拐弯内到达终点。
所以如果从一个方向到了一个点,我们需要比较当前的拐弯次数和从这个方向到这个点的最小拐弯次数的大小,如果当前的拐弯次数更少(也就是说当前路径方案是一个更优解的话),就将这个之前已经走过的点的此方向的最小拐弯次数更新,并且将这个点的拐弯次数变成更小的拐弯次数,并且放入队列更新。
然后不断的更新更优路径,如果当前的拐弯次数已经大于2了,就换另外一个点更新,如果能够在拐弯次数在2之内到达终点,就打标记。
这道题判断是否拐弯了的方法也很有意思,就是直接判断当前要走的方向和之前走到这个点的方向是否相同,相同的话就是没拐弯,否则就是拐弯了,如果是初始点的话就特判一下。
代码:
(目前只写明白了bfs的代码,dfs的待补):
#include<bits/stdc++.h>
#define ll long long
#define inf 0x3f3f3f3f
#define endl '\n'
#define IOS std::ios::sync_with_stdio(false),cin.tie(0), cout.tie(0)
using namespace std;
const int N = 1e3 + 5;
int n, m, f;
char a[N][N];
int vis[N][N][5];
struct node
{
int x, y;
int r, fx;
};
int dx[] = {1, 0, -1, 0};
int dy[] = {0, 1, 0, -1};
void bfs(int x, int y)
{
queue<node> Q;
node head;
head.x = x, head.y = y, head.fx = -1, head.r = 0;
Q.push(head);
while(Q.size() >= 1)
{
node now = Q.front();
Q.pop();
for(int i = 0; i < 4; i ++)
{
int tem; //下一步的拐弯次数
if(now.fx == -1) tem = 0; //初始方向
else if(now.fx == i) tem = now.r; //和之前的方向相同
else if(now.fx != i) tem = now.r + 1; //和之前的方向不同
int xx = now.x + dx[i], yy = now.y + dy[i];
if(xx < 0 || yy < 0 || xx >= n || yy >= m || tem > 2) continue; //可行性剪枝
if(a[xx][yy] =='T') //如果没超过两次拐弯就到达了终点的话,就做标记。
{
f = 1;
return ;
}
if(a[xx][yy] == '*') continue; //如果是墙的话就直接跳过。
if(tem >= vis[xx][yy][i]) continue; //如果之前此方向上的拐弯次数不大于此时,就跳过
vis[xx][yy][i] = tem; //如果此时是更优解的话,更新此方向上的vis数组
node next;
next.fx = i, next.r = tem, next.x = xx, next.y = yy;
Q.push(next);
}
}
return;
}
int main()
{
int kx, ky;
scanf("%d%d", &n, &m);
getchar();
for(int i = 0; i < n; i ++)
gets(a[i]);
for(int i = 0; i < n; i ++)
for(int j = 0; j < m; j ++)
if(a[i][j] == 'S') kx = i, ky = j;
memset(vis,0x3f,sizeof(vis));
bfs(kx, ky);
if(f)
printf("YES\n");
else
printf("NO\n");
return 0;
}