Saving James Bond - Hard Version

Saving James Bond - Hard Version

This time let us consider the situation in the movie "Live and Let Die" in which James Bond, the world's most famous spy, was captured by a group of drug dealers. He was sent to a small piece of land at the center of a lake filled with crocodiles. There he performed the most daring action to escape -- he jumped onto the head of the nearest crocodile! Before the animal realized what was happening, James jumped again onto the next big head... Finally he reached the bank before the last crocodile could bite him (actually the stunt man was caught by the big mouth and barely escaped with his extra thick boot).

Assume that the lake is a 100 by 100 square one. Assume that the center of the lake is at (0,0) and the northeast corner at (50,50). The central island is a disk centered at (0,0) with the diameter of 15. A number of crocodiles are in the lake at various positions. Given the coordinates of each crocodile and the distance that James could jump, you must tell him a shortest path to reach one of the banks. The length of a path is the number of jumps that James has to make.

Input Specification:

Each input file contains one test case. Each case starts with a line containing two positive integers N (≤ 100), the number of crocodiles, and D, the maximum distance that James could jump. Then N lines follow, each containing the (x,y) location of a crocodile. Note that no two crocodiles are staying at the same position.

Output Specification:

For each test case, if James can escape, output in one line the minimum number of jumps he must make. Then starting from the next line, output the position (x,y) of each crocodile on the path, each pair in one line, from the island to the bank. If it is impossible for James to escape that way, simply give him 0 as the number of jumps. If there are many shortest paths, just output the one with the minimum first jump, which is guaranteed to be unique.

Sample Input 1:

17 15
10 -21
10 21
-40 10
30 -50
20 40
35 10
0 -10
-25 22
40 -40
-30 30
-10 22
0 11
25 21
25 10
10 10
10 35
-30 10

Sample Output 1:

4
0 11
10 21
10 35

Sample Input 2:

4 13
-12 12
12 12
-12 -12
12 -12

Sample Output 2:

0

 

解题思路

  Easy Version的进阶版,要求把最短路径经过的点打印出来。

  这道题花了我很长的时间,一方面确实有些难度,但更主要的是我没有审题和写错数据啊!

  我没有看到题目中的“The length of a path is the number of jumps that James has to make.”,导致我一直以为路径长度是两点之间的欧氏距离,而实际上路径长度是跳跃的次数...

  而我发现这个问题后,还是有一个测试点一直没有过,调试了半天结果是因为有个数据写错了,本来正坐标轴的最大距离是50的,结果我写成了25。一改,代码立刻AC...

  我先来,我是傻逼...

  言归正传,这道题有很多的解法。我在网上看到有人多次调用Dijkstra算法,也有把所有可以跳跃的点用邻接矩阵存为一个图,再用Dijkstra算法,也有用BFS的。这里我是用BFS来找最短路径,原理和无权图的单源最短路算法几乎一样。

  先给出无权图的单源最短路算法的代码,其本质就是BFS:

 1 // 邻接表存储 - 无权图的单源最短路算法
 2 struct AdjVNode {
 3     int adjV;
 4     AdjVNode *next;
 5 };
 6 
 7 struct LGraph {
 8     AdjVNode *G[MAXN];
 9     int verN, edge;
10 };
11 
12 // dist[]和path[]全部初始化为-1
13 void Unweighted (LGraph *graph, int *dist, int *path, int src)
14 {
15     queue<int> q;
16     q.push(src);
17     dist[src] = 0;  // 初始化源点
18     
19     while(!q.empty()) {
20         int v = q.front();
21         q.pop();
22         for (AdjVNode *w = graph->G[V]; w; w = w->next) {   // 对v的每个邻接点w->adjV
23             if (dist[w->adjV] == -1) {                      // 若w->adjV未被访问过
24                 dist[w->adjV] = dist[v] + 1;                // w->adjV到src的距离更新
25                 path[w->adjV] = v;                          // 将v记录在src到w->adjV的路径上
26                 q.push(w->adjV);
27             }
28         }
29     }
30 }

  只不过在这道题我们并没有把点和距离存成图,所以判别两点是否有边,也就是能否从该点跳跃到另外一个点,的条件会有很大变化,而且有些复杂。

  在这道题的BFS中,我们需要先把输入的点遍历一遍,如果能够从起始位置(给定原点范围)跳跃到这个点,就把这个点对应的数组下标压入队列。这里要注意有个陷阱,就是如果这个点在给定的原点范围之内(也就是说如果有只鳄鱼在岸上),这个点是不能算的。

  然后把队列中的元素一个一个弹出,并重复操作:把输入的点遍历一遍,找到可以跳跃的点。如果可以跳到这个点:

  • 如果到这个点的跳跃次数更少的话,就更新这个点对应的dist和path数组的值。再把这个点压入队列。
  • 再判断是否可以从这个点跳跃到边界。如果可以的话:如果比之前记录的最小跳跃次数要小,则更新最小跳跃次数,同时记录这个点对应的数组下标;如果与之前记录的跳跃次数相同,则需要通过path数组找到对应的第一次跳跃的那个点,同时也要找到之前记录的最小跳跃次数那个点的对应的第一次跳跃的那个点,找到离原点最近的那个点,并记录这个点对应的数组下标。

  上面就是核心代码的主要框架,详细的AC代码如下:

  1 #include <cstdio>
  2 #include <cmath>
  3 #include <algorithm>
  4 #include <stack>
  5 #include <queue>
  6 
  7 const int INF = 0x3f3f3f3f;
  8 
  9 struct Point {
 10     int x, y;
 11 };
 12 
 13 int jumpDist(Point *point, int pos1, int pos2);
 14 int findSrc(int *path, int pos);
 15 int srcDist(Point *point, int pos);
 16 int BFS(Point *point, int *path, int n, int r);
 17 
 18 int main() {
 19     int n, r;
 20     scanf("%d %d", &n, &r);
 21     Point point[n];
 22     for (int i = 0; i < n; i++) {
 23         scanf("%d %d", &point[i].x, &point[i].y);
 24     }
 25     
 26     if (7.5 + r >= 50) {                    // 如果一步就可以跳到边界,直接输出1即可。最后一个测试点会卡这种情况 
 27         printf("1\n");
 28     }
 29     else {
 30         int path[n];                        // 数组的下标对应点的编号,用来记录该点的前一个点的编号 
 31         std::fill(path, path + n, -1);      // 初始化为-1代表没有点可以跳跃到该点 
 32         int minId = BFS(point, path, n, r); // 调用上述的BFS,返回的是跳跃的最后一个点的数组下标位置 
 33         if (minId == -1) {                  // 如果返回-1说明找不到可以到达边界的路线 
 34             printf("0\n");
 35         }
 36         else {
 37             std::stack<int> s;
 38             for (int i = minId; i != -1; i = path[i]) {
 39                 s.push(i);                  // 把整个跳跃过程经过的点的下标位置压入栈中,压的顺序是从终点到始点 
 40             }
 41             printf("%d\n", s.size() + 1);
 42             while (!s.empty()) {
 43                 int i = s.top();            // 把每个点的下标位置弹出来,打印的顺序是从始点到终点 
 44                 s.pop();
 45                 printf("%d %d\n", point[i].x, point[i].y);
 46             }
 47         }
 48     }
 49     
 50     return 0;
 51 }
 52 
 53 // 用来计算两个点的欧氏距离的平方 
 54 int jumpDist(Point *point, int pos1, int pos2) {
 55     return (point[pos1].x - point[pos2].x) * (point[pos1].x - point[pos2].x) + (point[pos1].y - point[pos2].y) * (point[pos1].y - point[pos2].y);
 56 }
 57 
 58 // 用来找到经过该点的路径的始点 
 59 int findSrc(int *path, int pos) {
 60     return path[pos] == -1 ? pos : findSrc(path, path[pos]);
 61 }
 62 
 63 // 用来计算该点与原点(0, 0)的欧氏距离的平方
 64 int srcDist(Point *point, int pos) {
 65     return point[pos].x * point[pos].x + point[pos].y * point[pos].y;
 66 }
 67 
 68 int BFS(Point *point, int *path, int n, int r) {
 69     int dist[n], minId = -1, minDist = INF; // min用来记录最小的跳跃次数,minId用来记录最小跳跃次数对应的那条路线的最后一个点 
 70     std::fill(dist, dist + n, INF);
 71     std::queue<int> q;
 72     for (int i = 0; i < n; i++) {
 73         int d = srcDist(point, i);
 74         if (d > 7.5 * 7.5 && d <= (7.5 + r) * (7.5 + r)) {  // 如果该点不再给定的原点范围内,并且可以从原点范围跳跃到该点
 75             q.push(i);      // 就把该点压入队列中 
 76             dist[i] = 1;    // 同时到该点的跳跃次数+1 
 77         }
 78     }
 79     
 80     while (!q.empty()) {    // 队列不为空 
 81         int tmp = q.front();
 82         q.pop();
 83         for (int i = 0; i < n; i++) {
 84             int d = jumpDist(point, tmp, i);
 85             // 如果不可以跳跃到这个点,或者这个点在给定原点范围之内,忽略这个点,走下一次的循环 
 86             if (d > r * r || srcDist(point, i) <= 7.5 * 7.5) continue;
 87             
 88             if (dist[tmp] + 1 < dist[i]) {  // 如果从tmp这个点跳跃到该点的次数比之前到该点的跳跃次数小 
 89                 dist[i] = dist[tmp] + 1;    // 更新为更小的跳跃次数 
 90                 path[i] = tmp;              // 同时把到该点点的前一个节点更新为tmp 
 91                 q.push(i);                  // 把该点压入队列 
 92             }
 93             
 94             if (50 - abs(point[i].x) <= r || 50 - abs(point[i].y) <= r) {   // 如果可以从该点跳跃到边界 
 95                 if (minDist > dist[i]) {    // 到该点的跳跃次数比之前记录的最小跳跃次数小 
 96                     minDist = dist[i];      // 更新最小跳跃次数 
 97                     minId = i;              // 更新对应路径的最后一个点 
 98                 }
 99                 else if (minDist == dist[i]) {          // 如果最小的跳跃次数相同 
100                     int src1 = findSrc(path, minId);    // 找到minId对应路径的的始点 
101                     int src2 = findSrc(path, i);        // 找到该点对应路径的始点 
102                     if (srcDist(point, src2) < srcDist(point, src1)) minId = i; // 如果该点对应路径的始点到原点的距离更小,更新minId 
103                 }
104             }
105         }
106     }
107     
108     return minId;
109 }

 

参考资料

  浙江大学——数据结构:https://www.icourse163.org/course/ZJU-93001?tid=1461682474

posted @ 2021-04-13 19:14  onlyblues  阅读(175)  评论(0编辑  收藏  举报
Web Analytics