816 - Abbott's Revenge

Abbott’s Revenge

PS:因为该题排版较麻烦,这里给出OJ网址:UVa816 - Abbott’s Revenge

有一个最多包含9*9个交叉点的迷宫。输入起点、离开起点时的朝向和终点,求一条最短路(多解时任意输出一个即可)。

这个迷宫的特殊之处在于:进入一个交叉点的方向(用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)。

816 - Abbott's Revenge_第1张图片

#include <cstdio>
#include <cstring>
#include <queue>
#include <vector>

using namespace std;

// 节点类
// 站在(row,column)上,朝向dir方
// dir的取值范围为[0-3],分别表示N,E,S,W
struct Node {
    // 行
    int row;
    // 列
    int column;
    // 朝向
    int dir;
    // 构造方法
    Node(int row = 0, int column = 0, int dir = 0):row(row),column(column),dir(dir){ }
};

const int maxNum = 10;
// 站在(i,j)面朝k方位,能否转向到m
// m取值范围为[0,2],0:向前,1:向左,2:向右
int has_edge[maxNum][maxNum][4][3];
// 初始点到(r,c,dir)的最短路径长度
int d[maxNum][maxNum][4];
// (r,c,dir)父节点的三元组
Node p[maxNum][maxNum][4];
// 初始点(r0,c0)及其朝向
int r0, c0, dir;
// 初始点向dir方向走了一步的位置(r1,c1)
int r1, c1;
// 出口位置(r2,c2)
int r2, c2;

// 朝向,上右下左
const char* dirs = "NESW";
// 转向,前左右
const char* turns = "FLR";
// 通过字符c来判断朝向
// 0:N 1:E 2:S 3:W
// 原理为strchr函数是在dirs中找c字符
// 找到,则返回指向c字符位置的指针
// 然后减去dirs的指针,就能获取相应位置了
int dir_id(char c) {
    return strchr(dirs, c) - dirs;
}

// 通过字符c来判断转向
// 0:F 1:L 2:R
// 原理和dir_id()一样
int turn_id(char c) {
    return strchr(turns, c) - turns;
}

// 位移变化数组
// 0:组合起来向上了一步
// 1:组合起来向右了一步
// 2:组合起来向下了一步
// 3:组合起来向左了一步
const int dr[] = {-1, 0, 1,  0};
const int dc[] = { 0, 1, 0, -1};

// 行走函数,通过该函数可以获得下一步的位置
Node walk(const Node &u, int turn) {
    int dir = u.dir;
    // 向左转
    if(turn == 1) {
        dir = (dir + 3) % 4;
    }
    // 向右转
    if(turn == 2) {
        dir = (dir + 1) % 4;
    }
    return Node(u.row + dr[dir], u.column + dc[dir], dir);
}

// 读取一组用例
bool readCase() {
    // 用例名和初始朝向
    char s[99], s2[99];
    if(scanf("%s%d%d%s%d%d", s, &r0, &c0, s2, &r2, &c2) != 6) {
        return false;
    }
    printf("%s\n", s);
    // 初始化初始节点的下一个节点
    dir = dir_id(s2[0]);
    r1 = r0 + dr[dir];
    c1 = c0 + dc[dir];

    memset(has_edge, 0, sizeof(has_edge));
    while(true) {
        // 行列
        int r, c;
        scanf("%d", &r);
        if(r == 0) {
            break;
        }
        scanf("%d", &c);
        while(scanf("%s", s) == 1 && s[0] != '*') {
            // s[0]为当前朝向
            // s[1-n]为能够转向的方向
            for(int i = 1; i < strlen(s); i++) {
                has_edge[r][c][dir_id(s[0])][turn_id(s[i])] = 1;
            }
        }
    }
    return true;
}

// 打印信息
void printAns(Node u) {
    // 从目标节点追溯到初始节点
    vector<Node> nodes;
    while(true) {
        nodes.push_back(u);
        // 初始节点的下一个节点退出
        if(d[u.row][u.column][u.dir] == 0) {
            break;
        }
        u = p[u.row][u.column][u.dir];
    }
    // 初始节点
    nodes.push_back(Node(r0, c0, dir));

    // 打印结果,每行10个
    // 每行最前面两个空格,(r,c)间一个空格
    int num = 0;
    for(int i = nodes.size() - 1; i >= 0; i--) {
        if(num % 10 == 0) {
            printf(" ");
        }
        printf(" (%d,%d)", nodes[i].row, nodes[i].column);
        if(++num % 10 == 0) {
            printf("\n");
        }
    }
    if(nodes.size() % 10 != 0) {
        printf("\n");
    }
}

// 是否在范围内
bool inside(int r, int c) {
    return r >= 1 && r <= 9 && c >= 1 && c <= 9;
}

// 采用BFS解决问题
void solve() {
    // 节点队列
    queue<Node> q;
    memset(d, -1, sizeof(d));

    Node u(r1, c1, dir);
    d[u.row][u.column][u.dir] = 0;
    q.push(u);
    while(!q.empty()) {
        Node u = q.front();
        q.pop();
        // 找到出口
        if(u.row == r2 && u.column == c2) {
            printAns(u);
            return;
        }
        // 0:F 1:L 2:R
        for(int i = 0; i < 3; i++) {
            // 转向方位都试探性的走一步
            Node v = walk(u, i);
            // 满足能够向该方向转
            // 移动之后还在边界内
            // 该点还没有被走过
            if(has_edge[u.row][u.column][u.dir][i] &&
               inside(v.row, v.column) && d[v.row][v.column][v.dir] < 0) {
                // 距离+1
                d[v.row][v.column][v.dir] = d[u.row][u.column][u.dir] + 1;
                // 设置父节点
                p[v.row][v.column][v.dir] = u;
                // 入队列
                q.push(v);
            }
        }
    }
    printf(" No Solution Possible\n");
}

int main () {
    while(readCase()) {
        solve();
    }
    return 0;
}

你可能感兴趣的:(ACM,uva,UVa816)