[算法竞赛入门经典] 万圣节后的早晨 UVa1601 详细注解 P322

本文探讨了一种解决迷宫中幽灵定位问题的高效算法。通过预处理减少搜索空间,利用状态压缩和BFS遍历策略,实现对三个幽灵在迷宫中的最优路径求解,避免了状态爆炸问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

被注释代码来自:

https://www.cnblogs.com/npugen/p/9539734.html

INPUT

OUTPUT

For each dataset in the input, one line containing the smallest number of steps to restore ghosts into the positions where they are supposed to be should be output. An output line should not contain extra characters such as spaces.

SAMPLE INPUT

SAMPLE OUTPUT

7

36

77

这个题的预处理也很值得学习。节点个数太多16*16=256,并且有三个鬼,所以状态总数几乎是256^3,不管是时间还是空间,肯定都要炸,所以就要把状态算的精确一些,省去一些不可能的情况,首先最外层都是'#',这样这个图就变成了最大14*14 = 196,还是有点大,注意题目条件,每2*2中至少有一个障碍,所以这196个点至多有75%是空格,196*0.75 = 147。这就差不多了,由于后面可能有加点的操作(鬼的数量不足3个需要加点),不过最多加2个点,也就是说总数不会超过150,这个数字就比较理想了,时间上和空间上都不错。判重的问题,刚才分析过了,每个鬼能够出现的格子数目不超过150,八位二进制数就能表示,因此三个就可以用24位二进制数表示,和int还有一段距离,所以转换成一个int型整数判重是再好不过的了。将所有空格连接起来用的是类似邻接表的操作,不过lrj(刘汝佳)没有用vector,可能还是效率原因吧。

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <queue>
using namespace std;

const int maxs = 20;
//((16-2)^2)*0.75
const int maxn = 150;

int n,m,cnt,sum;
//G可以定义成G[maxn][5]
//deg(degree) 图论中点连出的边数
int deg[maxn],G[maxn][maxn];
int x[maxn],y[maxn],id[maxn][maxn];
int d[maxn][maxn][maxn];
int s[5],t[5];
int dx[] = {0,0,0,-1,1};
int dy[] = {0,1,-1,0,0};

//三个鬼在非墙壁的位置可以用一个int编码
int find_ID(int a,int b,int c){
    return (a<<16)|(b<<8)|c;
}

bool conflict(int a,int b,int a2,int b2){
    //两个鬼是exchange位置(违反第2条)
    //两个鬼移动到同一个格子(违反第1条)
    if((a2==b && a==b2) || a2==b2) return true;
    return false;
}

//因为要寻找最短路径,所以用bfs
int bfs(){
    queue<int> que;
    //三个鬼初始的位置,对应的步数为0
    d[s[0]][s[1]][s[2]] = 0;
    que.push(find_ID(s[0],s[1],s[2]));
    while(!que.empty()){
        int top = que.front();que.pop();
        //解码,得到三个鬼在非墙壁格子的位置
        int a = (top>>16)&0xff,b = (top>>8)&0xff,c = (top&0xff);
        //找到!此位置等于三个鬼应该在的位置
        if(a==t[0] && b==t[1] && c==t[2]) return d[a][b][c];
        //a鬼可能移动到deg[a]个位置
        for(int i = 0;i < deg[a];i++){
            //a鬼移动到第a2个非墙壁格子
            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(d[a2][b2][c2] != -1) continue;
                    if(conflict(a,c,a2,c2)) continue;
                    if(conflict(b,c,b2,c2)) continue;
                    //步数加1
                    d[a2][b2][c2] = d[a][b][c]+1;
                    //编码后,压入堆栈
                    que.push(find_ID(a2,b2,c2));
                }
            }
        }
    }
    return -1;
}

int main()
{
    //freopen("input.txt","r",stdin);
    //freopen("output.txt","w",stdout);
    while(~scanf("%d%d%d\n",&m,&n,&sum) && (m||n||sum)){
        //input map
        char gra[maxs][maxs];
        cnt = 0;
        //三个鬼的位置?
        memset(d,-1,sizeof(d));
        for(int i = 0;i < n;i++) fgets(gra[i],maxs,stdin);

        for(int i = 0;i < n;i++){
            for(int j = 0;j < m;j++){
                if(gra[i][j] != '#'){
                    //x方向 非墙壁(可移动)
                    //y方向 非墙壁
                    //坐标是i,j的格子是第几个非墙壁的格子
                    x[cnt] = i,y[cnt] = j,id[i][j] = cnt;
                    //每个鬼各自初始的位置 source
                    if(islower(gra[i][j])) s[gra[i][j]-'a'] = cnt;
                    //每个鬼各自应该在的位置 target
                    else if(isupper(gra[i][j])) t[gra[i][j]-'A'] = cnt;
                    cnt++;
                }
            }
        }

        //每个非墙壁格子,在5种移动之后,成为第id[xx][yy]个非墙格
        for(int i = 0;i < cnt;i++){
            int X = x[i],Y = y[i];
            deg[i] = 0;
            for(int k = 0;k < 5;k++){
                int xx = X+dx[k],yy = Y+dy[k];
                if(gra[xx][yy] != '#') G[i][deg[i]++] = id[xx][yy];
            }
        }

        //最多三个鬼,dfs的时候没有三个鬼,也要按三个鬼去计算
        //少于3个鬼
        if(sum <= 2){
            //第2个鬼的初始位置、目标位置、可移动的位置都为cnt
            s[2] = t[2] = G[cnt][0] = cnt;
            //cnt点可移动的位置的个数为1,即只能呆在原地
            deg[cnt++] = 1;
        }
        //少于2个鬼
        if(sum <= 1){
            s[1] = t[1] = G[cnt][0] = cnt;
            deg[cnt++] = 1;
        }

        printf("%d\n",bfs());
    }
    return 0;
}

双向bfs

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <queue>
using namespace std;

const int maxs = 20;
const int maxn = 150;

int n,m,cnt,sum;
int deg[maxn],G[maxn][maxn];
int x[maxn],y[maxn],id[maxn][maxn];
int d[maxn][maxn][maxn];
int vis[maxn][maxn][maxn];
int s[5],t[5];
int dx[] = {0,0,0,-1,1};
int dy[] = {0,1,-1,0,0};

int find_ID(int a,int b,int c){
    return (a<<16)|(b<<8)|c;
}

bool conflict(int a,int b,int a2,int b2){
    if((a2==b && a==b2) || a2==b2) return true;
    return false;
}

int bfs(){
    queue<int> que,anti_que;
    d[s[0]][s[1]][s[2]] = 0;
    d[t[0]][t[1]][t[2]] = 0;

    que.push(find_ID(s[0],s[1],s[2]));
    anti_que.push(find_ID(t[0],t[1],t[2]));

    vis[s[0]][s[1]][s[2]] = 1;
    vis[t[0]][t[1]][t[2]] = 2;

    while(!que.empty() && !anti_que.empty()){
        int num = que.size(),anti_num = anti_que.size();
        while(num--){
            int top = que.front();que.pop();
            int a = (top>>16)&0xff,b = (top>>8)&0xff,c = (top&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)) continue;
                        if(conflict(b,c,b2,c2)) continue;

                        if(vis[a2][b2][c2] == 0){
                            d[a2][b2][c2] = d[a][b][c]+1;
                            vis[a2][b2][c2] = 1;
                            que.push(find_ID(a2,b2,c2));
                        }
                        else if(vis[a2][b2][c2] == 2){
                            //d[a][b][c]+1+d[a2][b2][c2]
                            return d[a][b][c]+d[a2][b2][c2]+1;
                        }
                    }
                }
            }
        }

        while(anti_num--){
            int top = anti_que.front();anti_que.pop();
            int a = (top>>16)&0xff,b = (top>>8)&0xff,c = (top&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)) continue;
                        if(conflict(b,c,b2,c2)) continue;

                        if(vis[a2][b2][c2] == 0){
                            d[a2][b2][c2] = d[a][b][c]+1;
                            vis[a2][b2][c2] = 2;
                            anti_que.push(find_ID(a2,b2,c2));
                        }
                        else if(vis[a2][b2][c2] == 1){
                            //d[a][b][c]+1+d[a2][b2][c2]
                            return d[a][b][c]+d[a2][b2][c2]+1;
                        }
                    }
                }
            }
        }
    }
    return -1;
}

int main()
{
    //freopen("input.txt","r",stdin);
    //freopen("output.txt","w",stdout);
    while(~scanf("%d%d%d\n",&m,&n,&sum) && (m||n||sum)){
        char gra[maxs][maxs];
        cnt = 0;
        memset(d,-1,sizeof(d));
        memset(vis,0,sizeof(vis));
        for(int i = 0;i < n;i++) fgets(gra[i],maxs,stdin);

        for(int i = 0;i < n;i++){
            for(int j = 0;j < m;j++){
                if(gra[i][j] != '#'){
                    x[cnt] = i,y[cnt] = j,id[i][j] = cnt;
                    if(islower(gra[i][j])) s[gra[i][j]-'a'] = cnt;
                    else if(isupper(gra[i][j])) t[gra[i][j]-'A'] = cnt;
                    cnt++;
                }
            }
        }

        for(int i = 0;i < cnt;i++){
            int X = x[i],Y = y[i];
            deg[i] = 0;
            for(int k = 0;k < 5;k++){
                int xx = X+dx[k],yy = Y+dy[k];
                if(gra[xx][yy] != '#') G[i][deg[i]++] = id[xx][yy];
            }
        }

        if(sum <= 2){
            s[2] = t[2] = G[cnt][0] = cnt;
            deg[cnt++] = 1;
        }
        if(sum <= 1){
            s[1] = t[1] = G[cnt][0] = cnt;
            deg[cnt++] = 1;
        }

        printf("%d\n",bfs());
    }
    return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值