例题6-14 Abbott的复仇(Abbott's Revenge, UVa 816)

欢迎访问我的Uva题解目录哦 https://blog.****.net/richenyunqi/article/details/81149109

题目描述

例题6-14 Abbott的复仇(Abbott's Revenge, UVa 816)

题意解析

有一个最多包含9*9个交叉点的迷宫。输入起点、离开起点时的朝向和终点,求一条最短路(多解时任意输出一个即可)。
以题目中的Figure 1为例,这个迷宫的特殊之处在于:进入一个交叉点的方向(用NEWS这4个字母分别表示北东西南,即上右左下)不同,允许出去的方向也不同。例如,1 2 WLF NR ER 表示交叉点(1,2)(上数第1行,左数第2列)有3个路标(字符“*”只是结束标志),如果进入该交叉点时的朝向为W(即朝左),则可以左转(L)或者直行(F);如果进入时朝向为N或者E则只能右转(R)。注意:初始状态是“刚刚离开入口”,所以即使出口和入口重合,最短路也不为空。例如,图6-14中的一条最短路为(3,1) (2,1) (1,1) (1,2) (2,2) (2,3) (1,3) (1,2) (1,1) (2,1) (2,2) (1,2) (1,3) (2,3) (3,3)。

算法设计

参考《算法竞赛入门经典(第2版)》中的分析:

本题和普通的迷宫在本质上是一样的,但是由于“朝向”也起到了关键作用,所以需要用一个三元组(r, c, dir)表示“位于(r,c),面朝dir”这个状态。假设入口位置为(r0, c0),朝向为dir,则初始状态并不是(r0, c0, dir),而是(r1, c1, dir),其中,(r1,c1)是(r0,c0)沿着方向dir走一步之后的坐标。此处用d[r][c][dir]表示初始状态到(r,c,dir)的最短路长度,并且用p[r][c][dir]保存了状态(r,c,dir)在BFS树中的父结点。
提示6-22:很多复杂的迷宫问题都可以转化为最短路问题,然后用BFS求解。在套用BFS框架之前,需要先搞清楚图中的“结点”包含哪些内容。
提示6-23:使用BFS求出图的最短路之后,可以用递归方式打印最短路的具体路径。如果最短路非常长,递归可能会引起栈溢出,此时可以改用循环,用vector保存路径。

我的算法的主要思想与书中类似,代码中添加了足够的注释,读者直接浏览我的代码即可。

C++代码

#include<bits/stdc++.h>
using namespace std;
struct Node{//结点类
    int r,c,d;//行、列、方向
    Node(int row=0,int col=0,int direc=0):r(row),c(col),d(direc){}
};
int direction[4][2]={{-1,0},{0,1},{1,0},{0,-1}};//NESW方向
unordered_map<char,int>trans={{'N',0},{'E',1},{'S',2},{'W',3},{'L',-1},{'R',1},{'F',0}};//字符与方向的映射
Node start,dest,past[10][10][4];//起点、终点、每个结点在最短路径中的父节点
vector<int>point[10][10][4];//每个结点处的路标及朝向
bool BFS(Node p){//广度优先搜索求最短路
    queue<Node>q;
    q.push(p);//入队
    bool inQueue[10][10][4]={false};//某结点是否已经进入队列
    inQueue[p.r][p.c][p.d]=true;//该结点已进入队列
    past[p.r][p.c][p.d]=start;//置父节点
    while(!q.empty()){//队列不空
        p=q.front();//出队
        q.pop();
        if(p.r==dest.r&&p.c==dest.c){//到达终点
            dest.d=p.d;//补充终点的朝向
            return true;//找到了最短路,返回true
        }
        if(p.r>0&&p.r<10&&p.c>0&&p.c<10&&!point[p.r][p.c][p.d].empty()){//观察该结点是否有该朝向的路标
            for(int i:point[p.r][p.c][p.d]){//遍历该路标下的转向
                int d=(p.d+i+4)%4,ii=p.r+direction[d][0],jj=p.c+direction[d][1];//求转向之后下一个节点
                if(ii>0&&ii<10&&jj>0&&jj<10&&!inQueue[ii][jj][d]){//下一个节点尚未入队
                    q.push(Node(ii,jj,d));//入队
                    inQueue[ii][jj][d]=true;//该结点已进入队列
                    past[ii][jj][d]=p;//置父节点
                }
            }
        }
    }
    return false;//找不到最短路,返回false
}
void DFS(Node v,vector<Node>&path){//深度优先搜索打印最短路径
    if(v.r==start.r&&v.c==start.c&&v.d==start.d){//到达了起点
        path.push_back(v);
        for(int i=0;i<path.size();++i)//打印
            printf("%s (%d,%d)",i%10==0?"\n ":"",path[path.size()-1-i].r,path[path.size()-1-i].c);
        puts("");
        return;
    }
    path.push_back(v);
    DFS(past[v.r][v.c][v.d],path);//深度优先搜索父节点
}
int main(){
    string s;
    while(getline(cin,s)&&s!="END"){
        for(int i=0;i<10;++i)
            for(int j=0;j<10;++j)
                for(int k=0;k<4;++k){
                    past[i][j][k]=Node();
                    point[i][j][k].clear();
                }
        printf("%s",s.c_str());
        cin>>start.r>>start.c>>s>>dest.r>>dest.c;
        start.d=-1;//到达起点时的朝向置为-1
        int a,b,d=trans[s[0]];
        while(scanf("%d%*c",&a)&&a!=0){
            scanf("%d",&b);
            while(cin>>s&&s!="*"){
                for(int j=1;j<s.size();++j)
                    point[a][b][trans[s[0]]].push_back(trans[s[j]]);
            }
        }
        if(BFS(Node(start.r+direction[d][0],start.c+direction[d][1],d))){//直接从起点的下一个节点开始搜索
            vector<Node>path;
            DFS(dest,path);
        }else
            puts("\n  No Solution Possible");
    }
    return 0;
}