程序来自:https://blog.csdn.net/qq_29169749/article/details/51420097
有1-3个东西用小写'a','b','c'表示,我们需要将他们分别移动到'A','B','C'位置,他们可以同时移动,但是不能相互穿过去,类似于不能从ab ,转换为ba状态,我们要求解的是最小的步数
我们直接看这个题的解法:首先是因为每个点有五种操作方式分别是:不动、上、下、左、右,那么如果直接使用BFS的话,每个点都有3^5的操作方式,显然这样这样一定会超时的,那么我们在这里可以使用第一个优化:我们将所有的不是墙的点的所有可以到达得到位置都保存下来,那么我们这样就会减少一些不必要的尝试。
然后我们进行BFS计算,但是他们三个可以同时移动,那么这里可以使用一个三维的数组,每一维度表示一个位置上的现在所在的位置,然后就可以进行BFS了
但是我们还不是很清楚每一次的状态怎么储存?我们可以用一个结构体,将每次的位置存起来,但是这个程序中用了一个更好的储存方法:我们知道最大的格数是16*16个,也就是256个,那么我们转换为二进制表示就是8位数,那么我们可以使用24位的二进制表示啊!然后我们再进行解压缩,所以这就是很神奇的地方!
最后还是有一个可以优化的地方就是:我们既然已经只带起点和终点了,并且还是用BFS求解,那么我们为什么不用双向BFS呢,这样还能节省一定的时间。
#include
#include
#include
#include
using namespace std;
int w, h, n, s[5], t[5];
char dataset[40][40];
int G[300][6], color[300][300][300], dist[300][300][300], redist[300][300][300];
int deg[300]; //记录每个编号为i的空格周围可以走的步数
int dx[] = {0, -1, 1, 0, 0};
int dy[] = {0, 0, 0, -1, 1};
int ID(int a, int b, int c) { //对状态进行编码,一个位置占8位
return (a << 16) | (b << 8) | c;
}
bool conflict(int a, int b, int a2, int b2) {//
return ((a2 == b2) || (a == b2 && b == a2));//移动到了同一个点上,或者是交换了位置等都是不合理的形式
}
int bfs() {
queue qf; //记录正向bfs
queue qb; //记录反向bfs
dist[s[0]][s[1]][s[2]] = 0;
dist[t[0]][t[1]][t[2]] = 1; //分别记录正反两种遍历走了多少步数
qf.push(ID(s[0], s[1], s[2]));
qb.push(ID(t[0], t[1], t[2])); //起点终点分别压入队列,压入队列的时候对点进行编码
color[s[0]][s[1]][s[2]] = 1;
color[t[0]][t[1]][t[2]] = 2; //分别标注正反两种遍历已经走过的,三个点同时动
while(!qf.empty() || !qb.empty()) {
int fnum = qf.size(), bnum = qb.size();
while(fnum--) {
int u = qf.front(); qf.pop();
int a = (u >> 16) & 0xff, b = (u >> 8) & 0xff, c = u & 0xff;//解码出出列状态三个小鬼的位置
for(int i = 0; i < deg[a];i++) {
int a2 = G[a][i];
for(int j = 0; j < deg[b]; j++) {
int b2 = G[b][j];
if(conflict(a, b, a2, b2)) continue;
for(int k = 0; k < deg[c]; k++) {
int c2 = G[c][k];
if(conflict(a, c, a2, c2) || conflict(b, c, b2, c2)) continue;
if(color[a2][b2][c2] == 0) {
dist[a2][b2][c2] = dist[a][b][c] + 1;
color[a2][b2][c2] = 1;
qf.push(ID(a2, b2, c2));
}
else if(color[a2][b2][c2] == 2) {
return dist[a][b][c] + dist[a2][b2][c2];
}
}
}
}
}
while(bnum--) {
int u = qb.front(); qb.pop();
int a = (u >> 16) & 0xff, b = (u >> 8) & 0xff, c = u & 0xff;//oxff:256 , 二进制 :11111111
for(int i = 0; i < deg[a]; i++) {
int a2 = G[a][i];
for(int j = 0; j < deg[b]; j++) {
int b2 = G[b][j];
if(conflict(a, b, a2, b2)) continue;
for(int k = 0; k < deg[c]; k++) {
int c2 = G[c][k];
if(conflict(a, c, a2, c2) || conflict(b, c, b2, c2)) continue;
if(color[a2][b2][c2] == 0) {
dist[a2][b2][c2] = dist[a][b][c] + 1;
color[a2][b2][c2] = 2;
qb.push(ID(a2, b2, c2));
}
else if(color[a2][b2][c2] == 1) {
return dist[a][b][c] + dist[a2][b2][c2];
}
}
}
}
}
}
return -1;
}
int main() {
while(~scanf("%d%d%d", &w, &h, &n) && w) {
getchar();
for(int i = 0; i < h; i++) gets(dataset[i]);
int cnt = 0, x[300], y[300], id[20][20]; //从图中抽取出空间并求出初始状态和目标状态,
for(int i = 0; i < h; i++)
for(int j = 0; j < w; j++) {
if(dataset[i][j] != '#') {
x[cnt] = i; y[cnt] = j; id[i][j] = cnt;//记录点的编号,xy数组根据编号得到点,id根据点得到编号
if(islower(dataset[i][j])) s[dataset[i][j] - 'a'] = cnt; //初始状态
else if(isupper(dataset[i][j])) t[dataset[i][j] - 'A'] = cnt; //目标状态
cnt++; //注意这里的cnt++不能偷懒在上面一行末尾,因为这样有时候cnt++会没有执行
}
}
//将所有的空格提出来建一张图,而不是临时去判断五种走法是否合法
for(int i = 0; i < cnt; i++) { //利用空格建立图,我们一共cnt个点,然后将每个点对应的五种情况都放进G图中,deg[i]表示这个点的五种情况哪一种可以走
deg[i] = 0;
for(int j = 0; j < 5; j++) {
int nx = x[i] + dx[j]; int ny = y[i] + dy[j];
if(dataset[nx][ny] != '#') G[i][deg[i]++] = id[nx][ny];//储存的是点的编号
}
}
if(n <= 2) { deg[cnt] = 1; G[cnt][0] = cnt; s[2] = t[2] = cnt++; }//没有第三个点,添加第三个点
if(n <= 1) { deg[cnt] = 1; G[cnt][0] = cnt; s[1] = t[1] = cnt++; }//只包含一个点,仍然满足的话,添加第二个点
memset(dist, 0, sizeof(dist));
memset(color, 0, sizeof(color));
if(s[0] == t[0] && s[1] == t[1] && s[2] == t[2]) printf("0\n");
else printf("%d\n", bfs());
}
return 0;
}
这里面的建图方法很神奇,以及解题的思想,也非常值得学习....