问题
问题描述
给定一个R行C列的地图,地图的每一个方格可能是'#', '+', '-', '|', '.', 'S', 'T'七个字符中的一个,分别表示如下意思:
'#': 任何时候玩家都不能移动到此方格;
'+': 当玩家到达这一方格后,下一步可以向上下左右四个方向相邻的任意一个非'#'方格移动一格;
'-': 当玩家到达这一方格后,下一步可以向左右两个方向相邻的一个非'#'方格移动一格;
'|': 当玩家到达这一方格后,下一步可以向上下两个方向相邻的一个非'#'方格移动一格;
'.': 当玩家到达这一方格后,下一步只能向下移动一格。如果下面相邻的方格为'#',则玩家不能再移动;
'S': 玩家的初始位置,地图中只会有一个初始位置。玩家到达这一方格后,下一步可以向上下左右四个方向相邻的任意一个非'#'方格移动一格;
'T': 玩家的目标位置,地图中只会有一个目标位置。玩家到达这一方格后,可以选择完成任务,也可以选择不完成任务继续移动。如果继续移动下一步可以向上下左右四个方向相邻的任意一个非'#'方格移动一格。
此外,玩家不能移动出地图。
请找出满足下面两个性质的方格个数:
1. 玩家可以从初始位置移动到此方格;
2. 玩家不可以从此方格移动到目标位置。
输入格式
输入的第一行包括两个整数R 和C,分别表示地图的行和列数。(1 ≤ R, C ≤ 50)。
接下来的R行每行都包含C个字符。它们表示地图的格子。地图上恰好有一个'S'和一个'T'。
输出格式
如果玩家在初始位置就已经不能到达终点了,就输出“I'm stuck!”(不含双引号)。否则的话,输出满足性质的方格的个数。
样例输入
5 5
--+-+
..|#.
..|##
S-+-T
####.
样例输出
2
样例说明
如果把满足性质的方格在地图上用'X'标记出来的话,地图如下所示:
--+-+
..|#X
..|##
S-+-T
####X
问题分析
性质1显而易见,使用DFS(BFS也行)即可找到从起点S出发能到达的所有点,将能到达的点记录下来:s_arrived[x][y] = true;
问题的难点在于性质2,暴力的做法可以遍历所有S可以到达的点({(x,y)|s_arrived[x][y] = true}),对每个点进行DFS(BFS也行)看其能否到达终点T并计数。但这样时间复杂度较高,下面给出另一种方法。
既然要找到不能到达T的点,那么我们可以先找能到达T的点并记录下来:t_arrived[x][y] = true;那么,什么点能到达T呢?便于理解,举例如下(A、B、C都是点,例子中不关心其坐标):
存在一条路径:S-->A-->B-->C-->T
第一步,我们可以先找C,显然它与T相邻(启发:第一步找与T相邻的点);第二步,找能到达C的点,显然它与C相邻;从这2步可以看出貌似可以递归地来找到所有能到达T的点,再深入想就会发现递归实现其实就是从T开始进行DFS,具体细节描述如下(为了叙述简便,把能到达X的点统称为destination(X)):
1.找到所有与X相邻的点,显然他们都是destination(X)的候选者;
2.检查这些候选者能否到达X,去掉那些不能到达X的候选者,剩下的就是可以到达X的点,即destination(X)。使用题中给出的样例作为例子:
T左边的(4,4)和T下边的(5,5)都可以作为X(令X=T)的destination的候选者,我们只需去掉那些不能到X的候选者,如(5,5),因为它是'.',所以它只能向下,不能到达T。
3.destination(T)、destination( destination(T) )、destination( destination( destination(T) ) )……即为所有能到达T的点。
求解过程中需要的量:X、与X相邻的候选者
候选者筛选规则:若候选者为‘-’,则需保证X与候选者在同一行;若候选者为‘|’,则需保证X与候选者在同一列;若候选者为‘.’,则需保证X在候选者的下方。
最后,符合性质1和性质2的点为:{(x,y)|s_arrived[x][y] = true && t_arrived[x][y] = false}.
代码
#include<cstdio>
#include<cstdlib>
#include<iostream>
using namespace std;
const int N = 50;
char map[N+1][N+1];
bool ban[N+1][N+1] = {false};
bool s_arrived[N+1][N+1] = {false};
bool t_arrived[N+1][N+1] = {false};
int r,c;
const int plus_sign[][2] = {{-1,0},{0,1},{1,0},{0,-1}};
const int minus_sign[][2] = {{0,-1},{0,1}};
const int stick_sign[][2] = {{-1,0},{1,0}};
const int dot_sign[][2] = {{1,0}};
bool IsLegal(int x,int y){
if(x<1||x>r||y<1||y>c)
return false;
return true;
}
//int test_s = 0;
//int test_t = 0;
void dfs_s(int x,int y){
if(!IsLegal(x,y)||ban[x][y]||s_arrived[x][y])
return;
s_arrived[x][y] = true;
// for(int i=0;i<test_s;i++)
// printf("\t");
// test_s++;
// printf("dfs_s(%d,%d)\n",x,y);
switch(map[x][y]){
case '+':
for(int i=0;i<4;i++)
dfs_s(x+plus_sign[i][0],y+plus_sign[i][1]);
break;
case '-':
for(int i=0;i<2;i++)
dfs_s(x+minus_sign[i][0],y+minus_sign[i][1]);
break;
case '|':
for(int i=0;i<2;i++)
dfs_s(x+stick_sign[i][0],y+stick_sign[i][1]);
break;
case '.':
dfs_s(x+dot_sign[0][0],y+dot_sign[0][1]);
break;
case 'S':
for(int i=0;i<4;i++)
dfs_s(x+plus_sign[i][0],y+plus_sign[i][1]);
break;
case 'T':
for(int i=0;i<4;i++)
dfs_s(x+plus_sign[i][0],y+plus_sign[i][1]);
break;
default:
printf("error!\n");
}
// test_s--;
}
void dfs_t(int x,int y,int last_x,int last_y){//last_x,last_y记录前一层(dfs)的点的x,y,用于分析反向情况
if(!IsLegal(x,y)||ban[x][y]||t_arrived[x][y])
return;
// for(int i=0;i<test_t;i++)
// printf("\t");
// test_t++;
// printf("dfs_t(%d,%d)\n",x,y);
if(map[x][y]=='-'&&last_x!=x)//保证同一行(不在同一行就return)
return;
else if(map[x][y]=='|'&&last_y!=y)//保证同一列(不在同一列就return)
return;
else if(map[x][y]=='.'&&last_x!=x+1)//保证last在x的下方(否则就return)
return;
t_arrived[x][y] = true;
for(int i=0;i<4;i++)
dfs_t(x+plus_sign[i][0],y+plus_sign[i][1],x,y);
// test_t--;
}
int main(){
int s_x,s_y,t_x,t_y,cnt = 0;
scanf("%d %d",&r,&c);
for(int i=1;i<=r;i++){
getchar();
for(int j=1;j<=c;j++){
scanf("%c",&map[i][j]);
if(map[i][j]=='#')
ban[i][j] = true;
else if(map[i][j]=='S'){
s_x = i;
s_y = j;
}
else if(map[i][j]=='T'){
t_x = i;
t_y = j;
}
}
}
//先从S开始找到所有能到达的点,结果存在s_arrived中
dfs_s(s_x,s_y);
if(!s_arrived[t_x][t_y]){
printf("I'm stuck!\n");
return 0;
}
dfs_t(t_x,t_y,t_x,t_y);
for(int i=1;i<=r;i++)
for(int j=1;j<=c;j++)
if(s_arrived[i][j]&&!t_arrived[i][j])
cnt++;
printf("%d\n",cnt);
return 0;
}
注意事项
1、代码的注释部分完全可以忽略,如果加上可以查看DFS的具体过程。
2、plus_sign[][2]、minus_sign[][2]、stick_sign[][2]和dot_sign[][2]这四个二维数组对应'+'、'-'、'|'、'.' 四种情况,用来确定允许进一步搜索的方向,完全可以使用if-else语句替换。
3、dfs_t的后2个参数记录的是问题分析中X的坐标。
有疑问欢迎提出!