UVA1601 _特殊的建图+双向BFS

程序来自: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 <iostream>
#include <cstdio>
#include <queue>
#include <cstring>

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<int> qf;  //记录正向bfs
    queue<int> 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;
}

这里面的建图方法很神奇,以及解题的思想,也非常值得学习....

阅读更多
想对作者说点什么?

博主推荐

换一批

没有更多推荐了,返回首页