本题并不是我自己想出来的,仅仅只是看懂了刘汝佳的源代码,然后自己想着敲了一遍,也许我写得不好,不过只要对大家有点帮助就还行。
本题运用到几个小技巧和大家分享一下:
1)添加虚拟节点,使它更方便BFS。
2)用二进制映射状态,方便入队和出队。
3)使用数组来变换方向
另外,假如你没有进行优化直接用原图BFS,那么肯定是会超时的,在这里就有一种优化方法。可以将原图存好后把字母和空格单独剔出来,因为‘#’它是不访问的。这样就可以减少你搜索的次数,以此来优化。还有就是在BFS时,如果用原数据BFS也肯定是会超时的,这时我们就可以用二进制来进行状态压缩,以此来减少很多入队和出队的时间。
下面是我的代码和注释
#include<cstdio>
#include<queue>
#include<cstring>
#include<cctype>
using namespace std;
const int maxn = 150;///空格占大约75%
const int maxs = 20;
const int dx[]={1,-1,0,0,0};///用于行上的位置变化
const int dy[]={0,0,1,-1,0};///用于列上的位置变化
int n,m,z,id[maxs][maxs];
int s[3],t[3],G[maxn][5],dig[maxn];
int d[maxn][maxn][maxn];///表示当前状态到原点的距离
int ID(int k1,int k2,int k3){
return (k1<<16)|(k2<<8)|k3;///用二进制压缩
}
bool pd(int a,int b,int a2,int b2){
return a2 == b2 || (a2 == b && b2 == a);
}
int bfs(){
queue<int >q;
memset(d,-1,sizeof(d));
q.push(ID(s[0],s[1],s[2]));///使三个初始节点映射成一个值,方便入队和出对
d[s[0]][s[1]][s[2]] = 0;///第一个状态到自己,距离为零
while(!q.empty()){
int u = q.front();q.pop();
int a = (u>>16)&0xff,b = (u>>8)&0xff,c = u&0xff;///分离出之前的三个节点
if(a == t[0] && b == t[1] && c == t[2])return d[a][b][c];///判断是否到达目标节点
for(int i = 0;i < dig[a]; i++){
int a2 = G[a][i];///第一个节点能到的节点
for(int j = 0;j < dig[b]; j++){
int b2 = G[b][j];///第二个节点能到的节点
if(pd(a,b,a2,b2))continue;///判断是否合法
for(int k = 0;k < dig[c]; k++){
int c2 = G[c][k];///第三个节点能到的节点
if(pd(a,c,a2,c2))continue;///判断是否合法
if(pd(b,c,b2,c2))continue;///判断是否合法
if(d[a2][b2][c2] != -1)continue;///剪枝
d[a2][b2][c2] = d[a][b][c] + 1;///移动一次
q.push(ID(a2,b2,c2));///移动后映射入队
}
}
}
}
return -1;
}
int main(){
while(scanf("%d%d%d",&n,&m,&z) == 3 && n){
char aa[20][20];
for(int i1 = 0;i1 <= m;i1++)
fgets(aa[i1],20,stdin);
///可以将空格也一起读入
int x[maxn],y[maxn];
int cnt = 0;///可以记录节点编号
for(int i = 0;i < m; i++){///行
for(int j = 0;j < n; j++){///列
if(aa[i][j] != '#'){
x[cnt] = i; ///记录该节点在那行
y[cnt] = j; ///记录该节点在那列
id[i][j] = cnt; ///用一个二维数组把节点编号存下来
if(islower(aa[i][j])){ ///判断该字符是否为小写字母
s[aa[i][j] - 'a'] = cnt;///记录给该字母的节点编号
}
else if(isupper(aa[i][j])){ ///判断该字符是否为大写字母
t[aa[i][j] - 'A'] = cnt;
}
cnt++;
}
}
}
for(int i = 0;i < cnt; i++){ ///开始遍历每个节点
dig[i] = 0; ///用来存能与该节点相连的节点的数量
for(int dir = 0;dir < 5; dir++){ ///方向变化
int nx = x[i] + dx[dir],ny = y[i] + dy[dir];///变化后的横纵坐标
if(aa[nx][ny] != '#'){
G[i][dig[i]++] = id[nx][ny]; ///记录该节点与他能到的节点,以此建图。
}
}
}
if(z <= 2){dig[cnt] = 1;G[cnt][0] = cnt;s[2] = t[2] = cnt++;}///添加虚拟节点
if(z <= 1){dig[cnt] = 1;G[cnt][0] = cnt;s[1] = t[1] = cnt++;}///添加虚拟节点
printf ("%d\n",bfs());
}
}
希望对你有帮助。