Abbottd的复仇,紫书P165UVa816

来源:互联网 发布:js多行文字溢出省略号 编辑:程序博客网 时间:2024/04/28 04:39

本题是一道非常复杂繁琐的bfs求最短路的题目,ACM2000年世界总决赛的原题目。其核心的bfs求最短路的算法比较经典,但是题目信息的处理与转换却极其复杂。下面根据刘汝佳的题解代码,详细分析其思路。

// UVa816 Abbott's Revenge// Rujia Liu#include<cstdio>#include<cstring>#include<vector>#include<queue>using namespace std;struct Node {  int r, c, dir; //   Node(int r=0, int c=0, int dir=0):r(r),c(c),dir(dir) {}};const int maxn = 10;const char* dirs = "NESW"; // const char* turns = "FLR";int has_edge[maxn][maxn][4][3];int d[maxn][maxn][4];Node p[maxn][maxn][4];int r0, c0, dir, r1, c1, r2, c2;int dir_id(char c) { return strchr(dirs, c) - dirs; }int turn_id(char c) { return strchr(turns, c) - turns; }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.r + dr[dir], u.c + dc[dir], dir);}bool inside(int r, int c) {  return r >= 1 && r <= 9 && c >= 1 && c <= 9;}bool read_case() {  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));  for(;;) {    int r, c;    scanf("%d", &r);    if(r == 0) break;    scanf("%d", &c);    while(scanf("%s", s) == 1 && s[0] != '*') {      for(int i = 1; i < strlen(s); i++)        has_edge[r][c][dir_id(s[0])][turn_id(s[i])] = 1;    }  }  return true;}void print_ans(Node u) {  //  vector<Node> nodes;  for(;;) {    nodes.push_back(u);    if(d[u.r][u.c][u.dir] == 0) break;    u = p[u.r][u.c][u.dir];  }  nodes.push_back(Node(r0, c0, dir));  //  int cnt = 0;  for(int i = nodes.size()-1; i >= 0; i--) {    if(cnt % 10 == 0) printf(" ");    printf(" (%d,%d)", nodes[i].r, nodes[i].c);    if(++cnt % 10 == 0) printf("\n");  }  if(nodes.size() % 10 != 0) printf("\n");}void solve() {  queue<Node> q;  memset(d, -1, sizeof(d));  Node u(r1, c1, dir);  d[u.r][u.c][u.dir] = 0;  q.push(u);  while(!q.empty()) {    Node u = q.front(); q.pop();    if(u.r == r2 && u.c == c2) { print_ans(u); return; }    for(int i = 0; i < 3; i++) {      Node v = walk(u, i);      if(has_edge[u.r][u.c][u.dir][i] && inside(v.r, v.c) && d[v.r][v.c][v.dir] < 0) {        d[v.r][v.c][v.dir] = d[u.r][u.c][u.dir] + 1;        p[v.r][v.c][v.dir] = u;        q.push(v);      }    }  }  printf("  No Solution Possible\n");}int main() {  while(read_case()) {    solve();  }  return 0;}

思路及语言实现分析:
1.输入数据读入及其处理。r0,c0变量保存起点坐标信息,r2,c2变量保存终点坐标信息。初始朝向用字符数组s2保存。其中对于方向信息的处理,刘用了的dir_id函数,将nesw四个方向信息转换成数字,保存在dir整形变量中。
2.编写了dr,dc坐标前进数组。这两个数组会根据当前朝向信息算出下一位置较当前位置横纵坐标的改变量。至此,根据初始位置和初始朝向已经计算出下一步的位置信息,并保存在r1,c1,dir中。
3.紧接着下一步保存路口转向信息。这是一件非常难处理的事情。刘巧妙的用了一个has_edge[r][c][dir_id(s[0])][turn_id(s[i])] = 1;hes_edge四维数组,分别表示站在(r,c)位置面向dir-id方向时是否可以往turn-id方向转。这样就巧妙的用一个四维数组解决了路口信息的处理,并且后续操作也因此十分方便。
4.接下来是程序的算法部分——bfs求最短路。刘在此时用到了一个自定义结构体类型node,其中分别是r,c,dir三个变量分别用来表示横纵坐标还有方向。因此,创建了一个node类型队列,然后创建一个node变量并且用r1,c1,dir初始化,然后入队。至此即将开始bfs。
5.bfs求最短路。此出贴上核心代码。

while(!q.empty()) {    Node u = q.front(); q.pop();    if(u.r == r2 && u.c == c2) { print_ans(u); return; }    for(int i = 0; i < 3; i++) {      Node v = walk(u, i);      if(has_edge[u.r][u.c][u.dir][i] && inside(v.r, v.c) && d[v.r][v.c][v.dir] < 0) {        d[v.r][v.c][v.dir] = d[u.r][u.c][u.dir] + 1;        p[v.r][v.c][v.dir] = u;        q.push(v);      }    }  }

其中walk函数是一个方向转变函数,根据做在坐标所朝的方向在给出往左前右转向的信息,来算出转换后的朝向并且返回转向后的下一个位置。inside()函数用来判断是否出界。d三维数组用来保存当前位置当前朝向的最小距离,p数组用来保存当前位置当前朝向的上一处节点。
反复遍历,直到某一路走到了终点。
5.输出结果。最后用一个数组,从结果节点根据p数组逐层往上查找保存节点信息。最后在反向打印即可输出最短路径信息。

非常有收获的题目,其中包含的无数信息的处理技巧,可以堪称经典。

原创粉丝点击