PTA 07-图5 Saving James Bond - Hard Version 题目分析及最优解法 测试点4最大N不通过原因分析

PTA-mooc完整题目解析及AC代码库:PTA(拼题A)-浙江大学中国大学mooc数据结构2020年春AC代码与题目解析(C语言)

写在最前面:各位测试点4最大N过不了的,回去看看是不是在计算距离时候用了sqrt函数。不要用sqrt函数或者距离全部用小数计算比较!!!


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

题解分析

这大概是我写PTA耗时最长的一道题,写了整整一天,最大N测试点都过不了,最后发现原因竟然是使用sqrt函数混用了整数和浮点数,傻了傻了。

题意说明

题目大意也是与简单版一样,这里重新复述一下:有一个100*100的正方形湖,湖中心有一个直径为15的小岛,坐标为(0, 0),湖东北角坐标为(50, 50)。湖中分散有很多鳄鱼,每个鳄鱼都在不同位置(即没有任何两条鳄鱼坐标相同)。这时007在湖心岛上,他想要通过不断地跳到鳄鱼头上,最终跳到岸上。

问题是:给定鳄鱼数量和所有鳄鱼坐标,再给定007跳跃的最远距离,求问007能否顺利逃脱?如果不能,输出0;如果能,并且有多条路径的话,输出最短的那条路(路径长短为跳跃次数,次数越少,路径越短)。此外,还需注意,如果多条最短路径,输出第一跳最短的那条路

解法分析

这道题本质上看,就是一个单源最短路径问题,因此把它看作一个BFS(广度优先遍历)问题即可。

切分整个问题,其实可以分为三块:

  1. 如何保存整个图。这道题只给出了图中结点,任意两个结点间是否有边需要通过能否跳跃来决定,这里我看到网上有做法是遍历了每两个结点,把所有关系存成了一个邻接表或者邻接矩阵的形式,此处我没有这么实现,而只是单纯按输入顺序存成一个数组。
  2. BFS过程。该步是整个算法核心,这里我把岸边看作一个结点,然后从能跳上岸的结点由外向内反向往湖心进行广度搜索,具体过程下面说明。
  3. 输出结果路径。因为我这里的path数组存储的是路径上的下一个结点编号,所以不需要额外栈空间来反向输出,普通顺序输出即可。

核心函数Save007(即BFS过程)说明

在说明整个过程之前,先说明一下由外向内比由内向外遍历的好处:

  1. 简化了考虑第一跳最短结点的情况,节省了空间。因为是由外向内的,所以第一跳是BFS最后一步才会考虑的,只需要要在最后一层上找一个跳跃最短的即可;而由内向外还需要额外存储每个路径的第一跳结点或者距离之类的。
  2. 输出方便。这一点显而易见,path不再保存该路径上一个结点编号而是保存下一个结点编号,因此输出时顺序输出即可,无需借助额外栈。
  3. 降低搜索遍历过程复杂性。考虑这样一种情况,同一层上的两个结点均可以跳到下一外层上某一结点,这种情况下如果由内到外遍历,该结点path上存储的上一结点位置还需要根据上一结点所在路径的第一跳距离来决定;而在由外向内搜索过程中,如果同一外层上两结点均可跳到内层上一结点,该结点的path只需存储第一个搜索到它的外层结点编号即可。(这里可以考虑一下具体原因,其实用到了题目中保证的“第一跳最短且是最短路径被保证是唯一的”这一条件)

具体过程就比较简单了,使用了一个队列和两个数组dist和path。

首先判断能否不经过鳄鱼,直接从岛上跳到岸上。如果不能,遍历所有结点,找到所有能跳上岸的鳄鱼,然后入队。之后执行普通的BFS过程即可。

这里有一点需要注意,如果某层的一个结点能跳到岸上时,不能立刻退出,应该遍历完该层所有结点,找到该层中离岸最近(即第一跳最短的)才退出。这里我用了一个cur_dis变量进行标识,意即某次出队结点与上一出队结点的路径长度不同时候,就说明上一层已经遍历结束了。

复杂度分析

这个解法不知道是不是最优解法,但是已经是我去网上比较了众多解法以后发现的最优解法,并且理解起来也非常容易,整体时间复杂度为 O ( n 2 ) O(n^2) ,空间复杂度为 O ( n ) O(n) 。最后在pta上最长耗时为2~4ms,最大占用内存为360~412kb,应该算是比较优的解法了。颇有种王婆卖瓜,自卖自夸的感觉了,whhh。

最后不得不说,sqrt函数这个问题让我最大N一直没通过,最后也算明白了要仔细仔细再仔细吧。


代码实现:(c语言)

#include <stdio.h>
#include <stdlib.h>
#include <math.h>

/* 队列定义开始 */
#define MaxSize 101
#define ERROR -1
typedef int Position;
struct QNode {
    int *Data;     /* 存储元素的数组 */
    Position Front, Rear;  /* 队列的头、尾指针 */
};
typedef struct QNode *Queue;

Queue CreateQueue()
{
    Queue Q = (Queue)malloc(sizeof(struct QNode));
    Q->Data = (int *)malloc(MaxSize * sizeof(int));
    Q->Front = Q->Rear = 0;
    return Q;
}

void DestoryQueue( Queue Q )
{
    if (Q->Data) free(Q->Data);
    free(Q);
}

int IsFull( Queue Q )
{
    return ((Q->Rear+1)%MaxSize == Q->Front);
}

void Enqueue( Queue Q, int X )
{
    if ( IsFull(Q) ) return;
    else {
        Q->Rear = (Q->Rear+1)%MaxSize;
        Q->Data[Q->Rear] = X;
    }
}

int IsEmpty( Queue Q )
{
    return (Q->Front == Q->Rear);
}

int Dequeue( Queue Q )
{
    if ( IsEmpty(Q) ) return ERROR;
    else  {
        Q->Front =(Q->Front+1)%MaxSize;
        return  Q->Data[Q->Front];
    }
}
/* 队列定义结束 */

#define MaxVertexNum 100
#define INF 65535
#define ISLAND_DIAMETER 15/2.0
typedef struct Position Point;
struct Position {
    int x;
    int y;
};
Point points[MaxVertexNum];
int dist[MaxVertexNum];    // 路径长度
int path[MaxVertexNum];  // 下一个结点

void ReadPoints(int num);

int Distance(Point p1, Point p2);   //求两点间距离
int Jump(Point p1, Point p2, int dis);  // 判断能否跳跃过去
int isSafe(Point p, int dis);   // 是否能跳上岸
void PrintPath(int ans);
void Save007(int num, int dis); // 其实就是一个BFS函数

int main()
{
    int num, dis;
    scanf("%d %d", &num, &dis);

    ReadPoints(num);
    Save007(num, dis);

    return 0;
}

void ReadPoints(int num)
{
    int i;
    for (i = 0; i < num; ++i) {
        scanf("%d %d", &(points[i].x), &(points[i].y));
    }
}

int Distance(Point p1, Point p2)
{
    return (p1.x - p2.x) * (p1.x - p2.x) + (p1.y - p2.y) * (p1.y - p2.y);
}

int Jump(Point p1, Point p2, int dis)
{
    return Distance(p1, p2) <= dis * dis;
}

int isSafe(Point p, int dis)
{
    return ((abs(p.x) + dis) >= 50 || (abs(p.y) + dis) >= 50);
}

void PrintPath(int ans)
{
    printf("%d\n", dist[ans] + 1);
    for (; ans != -1; ans = path[ans]) {
        printf("%d %d\n", points[ans].x, points[ans].y);
    }
}

void Save007(int num, int dis)
{
    int i, cur_dis, pIndex, ans;
    Queue Q;
    Point center;
    center.x = 0; center.y = 0;

    if (isSafe(center, dis + ISLAND_DIAMETER)) {    // 如果从岛上就能直接跳上岸
        printf("1\n");
        return;
    }

    for (i = 0; i < num; ++i) {     // 初始化辅助数组
        dist[i] = INF;
        path[i] = -1;
    }
    Q = CreateQueue();
    for (i = 0; i < num; ++i) {
        if (isSafe(points[i], dis)) {   // 将所有能上岸的入队
            dist[i] = 1;
            Enqueue(Q, i);
        }
    }
    cur_dis = 1; ans = -1;
    while (!IsEmpty(Q)) {
        pIndex = Dequeue(Q);
        if (dist[pIndex] == cur_dis + 1 && ans != -1) break;  // 已经遍历完当前层
        if (Jump(center, points[pIndex], dis + ISLAND_DIAMETER)) {
            if (ans == -1 || Distance(center, points[pIndex]) < Distance(center, points[ans]))
                ans = pIndex;
        }
        else {
            for (i = 0; i < num; ++i) {
                if (Jump(points[pIndex], points[i], dis) && (dist[pIndex] + 1 < dist[i])) {
                    dist[i] = dist[pIndex] + 1;
                    path[i] = pIndex;
                    Enqueue(Q, i);
                }
            }
        }
        cur_dis = dist[pIndex];
    }
    if (ans == -1) printf("0\n");
    else PrintPath(ans);
    DestoryQueue(Q);
}

发布了14 篇原创文章 · 获赞 18 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/zhuiyisinian/article/details/105328571