UVA1601 POJ 3523 状态压缩 预处理图 分别使用单向双向BFS和A*搜索

Description

You are working for an amusement park as an operator of an obakeyashiki, or a haunted house, in which guests walk through narrow and dark corridors. The house is proud of their lively ghosts, which are actually robots remotely controlled by the operator, hiding here and there in the corridors. One morning, you found that the ghosts are not in the positions where they are supposed to be. Ah, yesterday was Halloween. Believe or not, paranormal spirits have moved them around the corridors in the night. You have to move them into their right positions before guests come. Your manager is eager to know how long it takes to restore the ghosts.

In this problem, you are asked to write a program that, given a floor map of a house, finds the smallest number of steps to move all ghosts to the positions where they are supposed to be.

A floor consists of a matrix of square cells. A cell is either a wall cell where ghosts cannot move into or a corridor cell where they can.

At each step, you can move any number of ghosts simultaneously. Every ghost can either stay in the current cell, or move to one of the corridor cells in its 4-neighborhood (i.e. immediately left, right, up or down), if the ghosts satisfy the following conditions:

  1. No more than one ghost occupies one position at the end of the step.

  2. No pair of ghosts exchange their positions one another in the step.

For example, suppose ghosts are located as shown in the following (partial) map, where a sharp sign (‘#’) represents a wall cell and ‘a’, ‘b’, and ‘c’ ghosts.

####
 ab#
#c##
####

The following four maps show the only possible positions of the ghosts after one step.

####
 ab#
#c##
####
 
####
a b#
#c##
####
 
####
acb#
# ##
####
 
####
ab #
#c##
####

Input

The input consists of at most 10 datasets, each of which represents a floor map of a house. The format of a dataset is as follows.

whn 
c11c12c1w
c21c22c2w
ch1ch2chw

wh and n in the first line are integers, separated by a space. w and h are the floor width and height of the house, respectively. n is the number of ghosts. They satisfy the following constraints.

4 ≤ w ≤ 16, 4 ≤ h ≤ 16, 1 ≤ n ≤ 3

Subsequent h lines of w characters are the floor map. Each of cij is either:

  • a ‘#’ representing a wall cell,

  • a lowercase letter representing a corridor cell which is the initial position of a ghost,

  • an uppercase letter representing a corridor cell which is the position where the ghost corresponding to its lowercase letter is supposed to be, or

  • a space representing a corridor cell that is none of the above.

In each map, each of the first n letters from a and the first n letters from A appears once and only once. Outermost cells of a map are walls; i.e. all characters of the first and last lines are sharps; and the first and last characters on each line are also sharps. All corridor cells in a map are connected; i.e. given a corridor cell, you can reach any other corridor cell by following corridor cells in the 4-neighborhoods. Similarly, all wall cells are connected. Any 2 × 2 area on any map has at least one sharp. You can assume that every map has a sequence of moves of ghosts that restores all ghosts to the positions where they are supposed to be.

The last dataset is followed by a line containing three zeros separated by a space.

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

5 5 2
#####
#A#B#
#   #
#b#a#
#####
16 4 3
################
## ########## ##
#    ABCcba    #
################
16 16 3
################
### ##    #   ##
##  #  ##   # c#
#  ## ########b#
# ##  # #   #  #
# #  ##   # # ##
##  a#  # # #  #
### ## #### ## #
##   #   #  #  #
#  ##### # ## ##
####   #B# #   #
##  C#   #   ###
#  # # ####### #
# ######  A##  #
#        #    ##
################
0 0 0

Sample Output

7
36
77

任何一个 2*2子网格中至少有一个障碍格,所以我们将可以通行的点建一张图

最简单的版本 单向BFS:

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cctype>
#include <queue>
using namespace std;

const int maxs = 20;
const int maxn = 150;//75%的方格可通行
const int dx[] = {1,-1,0,0,0};
const int dy[] = {0,0,1,-1,0};

inline int ID(int a, int b, int c){//状态压缩,把abc三个编号压缩为一个int
    return (a<<16)|(b<<8)|c; //生成ID用|
}

int s[3],t[3];
int d[maxn][maxn][maxn];
int deg[maxn],G[maxn][5];

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

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; // 状态还原: 解析ID用&
        if(a==t[0]&&b==t[1]&&c==t[2])return d[a][b][c];//搜到答案
        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(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(){
    int w,h,n;
    while(~scanf("%d%d%d\n",&w,&h,&n)){
        if(!n)break;
        char maze[20][20];
        for(int i=0;i<h;i++)
            fgets(maze[i],20,stdin);  
        int cnt=0,x[maxn],y[maxn],id[maxs][maxs];
        for(int i=0;i<h;i++)
            for(int j=0;j<w;j++)
               if(maze[i][j]!='#'){//给每个非墙的cell编号
                  x[cnt]=i;y[cnt]=j;
                  id[i][j]=cnt;//将编号存到对应的坐标里
                  if(islower(maze[i][j])) s[maze[i][j]-'a']=cnt;
                  else if(isupper(maze[i][j]))t[maze[i][j]-'A']=cnt;
                  cnt++;//更新编号
        }
        for(int i=0;i<cnt;i++){
            deg[i]=0;//清空每个点的相邻边计数器
            for(int j=0;j<5;j++){
                int nx=x[i]+dx[j],ny=y[i]+dy[j];
                if(maze[nx][ny]!='#')G[i][deg[i]++]=id[nx][ny];//建立G 从边到坐标点的映射
            }
        }                                                     //当n<=2时
        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++;}
        printf("%d\n",bfs());       //虚物体的坐标在地图外,且只有通向自身坐标的一条边
    }
    return 0;
}

将单向BFS稍加修改得到双向BFS:

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cctype>
#include <queue>
using namespace std;

const int maxs = 20;
const int maxn = 150;//75%的方格可通行
const int dx[] = {1,-1,0,0,0};
const int dy[] = {0,0,1,-1,0};

inline int ID(int a, int b, int c){//状态压缩,把abc三个编号压缩为一个int
    return (a<<16)|(b<<8)|c; //生成ID用 |
}

int s[3],t[3];
int deg[maxn],G[maxn][5];
int vis[maxn][maxn][maxn],d[maxn][maxn][maxn];
inline bool conflict (int a, int b, int a2, int b2){
    return a2==b2||(a==b2&&b==a2);
}

int bfs(){
    queue<int> qa;
    queue<int> qb;
    memset(vis,0,sizeof(vis));
    memset(d,0,sizeof(d));
    d[s[0]][s[1]][s[2]]=0;
    d[t[0]][t[1]][t[2]]=1; //分别记录正反两个方向bfs的路径长度,注意要+1
    qa.push(ID(s[0],s[1],s[2]));
    qb.push(ID(t[0],t[1],t[2]));
    vis[s[0]][s[1]][s[2]]=1;
    vis[t[0]][t[1]][t[2]]=2;
    while(!qa.empty()||!qb.empty()){
        int sizea=qa.size(),sizeb=qb.size();
        while(sizea--){     //正向搜索一层
            int u=qa.front();qa.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(vis[a2][b2][c2]==0){
                                d[a2][b2][c2]=d[a][b][c]+1;
                                vis[a2][b2][c2]=1;//正向搜索经过的状态标记为1
                                qa.push(ID(a2,b2,c2));
                            }
                            else if(vis[a2][b2][c2]==2) return d[a][b][c]+d[a2][b2][c2];
                    } //检测是否正反交汇
                }
            }
        }
        while(sizeb--){      //反向搜索一层
            int u=qb.front();qb.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(vis[a2][b2][c2]==0){
                                d[a2][b2][c2]=d[a][b][c]+1;
                                vis[a2][b2][c2]=2;//反向搜索经过的状态标记为2
                                qb.push(ID(a2,b2,c2));
                            }
                            else if(vis[a2][b2][c2]==1) return d[a][b][c]+d[a2][b2][c2];
                    } //检测是否正反交汇
                }
            }
        }
    }
}

int main(){
    //freopen("datain.txt","r",stdin);
    int w,h,n;
    while(~scanf("%d%d%d\n",&w,&h,&n)){
        if(!n)break;
        char maze[20][20];
        for(int i=0;i<h;i++){
            fgets(maze[i],20,stdin);
        }
        int cnt=0,x[maxn],y[maxn],id[maxs][maxs];
        for(int i=0;i<h;i++)
            for(int j=0;j<w;j++)
        if(maze[i][j]!='#'){
            x[cnt]=i;y[cnt]=j;
            id[i][j]=cnt;
            if(islower(maze[i][j])) s[maze[i][j]-'a']=cnt;
            if(isupper(maze[i][j])) t[maze[i][j]-'A']=cnt;
            cnt++;
        }
        for(int i=0;i<cnt;i++){
            deg[i]=0;
            for(int j=0;j<5;j++){
                int nx=x[i]+dx[j],ny=y[i]+dy[j];
                if(maze[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++;}
        printf("%d\n",bfs());
    }
}

受到LCCWAID大神的启发的A*版本:

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cctype>
#include <queue>
using namespace std;

const int maxs = 20;
const int maxn = 150;//75%的方格可通行
const int dx[] = {1,-1,0,0,0};
const int dy[] = {0,0,1,-1,0};

struct Node{  //A* 用
    int state, step, pre; step:步数g,pre:估值函数h
    Node(int a, int b, int c): state(a),step(b),pre(c){}
    bool operator < (const Node &rhs)const{
        return step + pre > rhs.step + rhs.pre;
    }
};

inline int ID(int a, int b, int c){//状态压缩,把abc三个编号压缩为一个int
    return (a<<16)|(b<<8)|c; //生成ID用|
}

int s[3],t[3];       //起点坐标和终点坐标
int dis[3][maxn+1];  //预处理的每个点到ABC三点的最短距离
int deg[maxn],G[maxn][5];
int buf[maxn][maxn][maxn]; //保存启发函数
inline bool conflict (int a, int b, int a2, int b2){
    return a2==b2||(a==b2&&b==a2);
}


void pre_h(int op)  //预处理h函数(距离)
{
    queue<int> Q;
    int start = t[op];
    Q.push(start);
    dis[op][start] = 0;
    while(!Q.empty()){
        int x = Q.front();Q.pop();
        for(int i = 0; i < deg[x]; i ++){
            int nx = G[x][i];
            if(dis[op][nx] > dis[op][x] + 1){
                dis[op][nx] = dis[op][x] + 1;
                Q.push(nx);
            }
        }
    }
}

int heu(int id){     //heuristic  预估还需要走的距离
    int a = (id>>16)&0xff, b = (id>>8)&0xff, c = id&0xff; //状态还原, & 运算
    return max(dis[0][a],max(dis[1][b],dis[2][c]));
}

int Astar(){
    priority_queue<Node> q;
    int start = ID(s[0],s[1],s[2]);
    q.push(Node(start,0,heu(start)));
    buf[s[0]][s[1]][s[2]] = heu(start);
    while(!q.empty()){
            Node cur = q.top(); q.pop();
            int a = (cur.state>>16)&0xff, b = (cur.state>>8)&0xff, c = cur.state&0xff;
            if(cur.step + cur.pre > buf[a][b][c])continue;
            if(a==t[0]&&b==t[1]&&c==t[2])  return cur.step;
            for(int i=0;i<deg[a];i++){ //枚举a的移动
                int a2 = G[a][i];
                for(int j=0;j<deg[b];j++){ //枚举b的移动
                    int b2 = G[b][j];
                    if(conflict(a,b,a2,b2))continue;
                    for(int k=0;k<deg[c];k++){ //枚举c的移动
                        int c2 = G[c][k];
                        if(conflict(a,c,a2,c2))continue;
                        if(conflict(b,c,b2,c2))continue; //三个物体的移动两两之间不能冲突
                        int t = heu(ID(a2,b2,c2));
                        if(cur.step +1 + t < buf[a2][b2][c2]){
                            buf[a2][b2][c2] = cur.step +1 + t;
                            q.push(Node(ID(a2,b2,c2),cur.step+1,t));
                        }
                    }
                }
            }
    }
}

int main(){
    //freopen("datain.txt","r",stdin);
    int w,h,n;
    while(~scanf("%d%d%d\n",&w,&h,&n)){
        if(!n)break;
        char maze[20][20];
        for(int i=0;i<h;i++)
            fgets(maze[i],20,stdin);
        int cnt=0,x[maxn],y[maxn],id[maxs][maxs];
        for(int i=0;i<h;i++)
            for(int j=0;j<w;j++)
               if(maze[i][j]!='#'){//给每个非墙的cell编号
                  x[cnt]=i;y[cnt]=j;
                  id[i][j]=cnt;//将编号存到对应的坐标里
                  if(islower(maze[i][j])) s[maze[i][j]-'a']=cnt;
                  else if(isupper(maze[i][j]))t[maze[i][j]-'A']=cnt;
                  cnt++;//更新编号
        }
        for(int i=0;i<cnt;i++){
            deg[i]=0;//清空每个点的相邻边计数器
            for(int j=0;j<5;j++){
                int nx=x[i]+dx[j],ny=y[i]+dy[j];
                if(maze[nx][ny]!='#')G[i][deg[i]++]=id[nx][ny];//建立G 从边到坐标点的映射
            }
        }                                                     //当n<=2时
        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(dis,1,sizeof dis);          //初始化为0则pre_h函数无法更新dis数组
        memset(buf,1,sizeof buf);          //初始化为0则A* 无法正常进行
        pre_h(0); pre_h(1); pre_h(2);
        //printf("%d\n",dis[2][4]);
        printf("%d\n",Astar());
    }
    return 0;
}

 

下面是使用单向BFS和A*搜索所耗时间差别

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值