07-图5 Saving James Bond

题目描述

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

解题思路

根据题目描述,可以看出这是一个单源无权图的最短路径问题,因此主要用BFS来解决此问题(不是Dijkstra算法)。由于要求输出最短路径长度以及经过的顶点坐标,那么需要使用两个数组存储这些信息,我这里使用的是distancepath。BFS的思路不必多说,需要注意的是如果能够跳到岸上需要提前退出,然后返回最后一个鳄鱼的下标(从1开始),如果不能跳到岸上返回0。这道题的难点在于如果有多个路径长度相同的最短路径,只能输出第一个鳄鱼距离最近的那种情况。我们可以先访问一遍能够第一次跳到的鳄鱼,把它们存在一个数组中并按照距离递增排序,然后按照这个顺序BFS,如果不能到岸上则重置distancepath,然后以下一个元素为起点BFS;如果能到岸上则提前返回最后一个鳄鱼下标。

代码

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

#define MAXSIZE 101

typedef struct {
    int x;
    int y;
} Coordinate;   //鳄鱼的坐标

typedef struct {
    int dis;    //距离的平方
    int index;  //对应到鳄鱼数组的下标
} FirstNode;    //存第一次跳的鳄鱼

typedef struct QNode {
    int index;
    struct QNode *next;
} QNode, *Queue;    //队列

int distance[MAXSIZE];
int path[MAXSIZE];

Queue createQueue();
void pushQueue(Queue queue, int data);
int popQueue(Queue queue);
int sizeQueue(Queue queue);
bool emptyQueue(Queue queue);
void quickSort(FirstNode a[], int low, int high);
int firstJump(Coordinate crocodiles[], int N, int D);
int BFS(Coordinate crocodiles[], int N, int D, int first);
bool canFirstJump(int x, int y, int D);
bool canJump(int x1, int y1, int x2, int y2, int D);
bool canEscape(int x, int y, int D);

int main() {
    int N, D, x, y;
    scanf("%d %d", &N, &D);

    if (D + 7.5 >= 50) {    //直接跳到岸上,不踩鳄鱼,应对测试点5
        printf("1
");
        return 0;
    }

    Coordinate crocodiles[MAXSIZE];
    for (int i = 1; i <= N; i++) {
        scanf("%d %d", &x, &y);
        crocodiles[i].x = x;
        crocodiles[i].y = y;
        distance[i] = -1;   //顺便把这个数组也初始化了
        path[i] = -1;       //顺便把这个数组也初始化了
    }
    int index = firstJump(crocodiles, N, D);
    if (index) {        //能跳到岸上
        int dis = distance[index] + 1;
        int n = 0, ret[MAXSIZE];
        while (index != -1) {
            ret[n++] = index;
            index = path[index];    //index变为前一个顶点的index
        }
        printf("%d
", dis);
        for (int i = n - 1; i >= 0; i--) {
            printf("%d %d
", crocodiles[ret[i]].x, crocodiles[ret[i]].y);
        }
    } else {            //不能跳到岸上
        printf("0
");
    }
    return 0;
}

//创建队列,返回链表的哨兵结点
Queue createQueue() {
    Queue queue = (Queue) malloc(sizeof(QNode));
    queue->index = 0;
    queue->next = NULL;
    return queue;
}

//入队,尾插法
void pushQueue(Queue queue, int data) {
    Queue rear = queue;
    while (rear->next) rear = rear->next;
    Queue newNode = (Queue) malloc(sizeof(QNode));
    newNode->index = data;
    newNode->next = NULL;
    rear->next = newNode;
}

//出队,删除链表第一个结点
int popQueue(Queue queue) {
    if (emptyQueue(queue)) return 0;
    Queue deleteNode = queue->next;
    int ret = deleteNode->index;
    queue->next = deleteNode->next;
    free(deleteNode);
    return ret;
}

//队列是否为空
bool emptyQueue(Queue queue) {
    return queue->next == NULL;
}

//快速排序,递增顺序
void quickSort(FirstNode a[], int low, int high) {
    if (low >= high) return;
    int i = low, j = high;
    int tmpDis = a[low].dis;
    int tmpIndex = a[low].index;
    while (i < j) {
        while (i < j && tmpDis <= a[j].dis) j--;
        a[i] = a[j];
        while (i < j && a[i].dis <= tmpDis) i++;
        a[j] = a[i];
    }
    a[i].dis = tmpDis;
    a[i].index = tmpIndex;
    quickSort(a, low, i - 1);
    quickSort(a, i + 1, high);
}


//第一次跳,用一个数组存第一下跳能够跳到的鳄鱼的距离的平方和下标
int firstJump(Coordinate crocodiles[], int N, int D) {
    FirstNode first[MAXSIZE];
    int j = 0, x, y;
    for (int i = 1; i <= N; i++) {
        x = crocodiles[i].x;
        y = crocodiles[i].y;
        if (canFirstJump(x, y, D)) {
            first[++j].dis = pow(x, 2) + pow(y, 2);
            first[j].index = i;
        }
    }
    quickSort(first, 1, j);             //将first按照距离的平方递增排序
    for (int i = 1; i <= j; i++) {      //按照从小到大的顺序BFS
        int index = BFS(crocodiles, N, D, first[i].index);
        if (index) {
            return index;
        } else {    //如果以这个鳄鱼为第一个顶点跳不到岸上,就要重置下面这两个数组
            for (int j = 1; j <= N; j++) {
                distance[j] = -1;
                path[j] = -1;
            }
        }
    }
    return 0;
}

//广度优先搜索
int BFS(Coordinate crocodiles[], int N, int D, int first) {
    int x1, y1, x2, y2;
    Queue queue = createQueue();
    pushQueue(queue, first);
    distance[first] = 1;    //将第一个鳄鱼的距离赋值为1
    while (!emptyQueue(queue)) {    
        int index = popQueue(queue);
        x1 = crocodiles[index].x;
        y1 = crocodiles[index].y;
        if (canEscape(x1, y1, D)) return index;     //能跳到岸上,就提前返回当前鳄鱼下标
        for (int j = 1; j <= N; j++) {
            x2 = crocodiles[j].x;
            y2 = crocodiles[j].y;
            if (distance[j] == -1 && canJump(x1, y1, x2, y2, D)) {
                distance[j] = distance[index] + 1;      //更新距离
                path[j] = index;                        //更新路径
                pushQueue(queue, j);
            }
        }   
    }
    return 0;       //不能跳到岸上就返回0,由于下标是从1开始的,因此可以以此判断是否成功跳到岸上
}

//判断是否能从岛上跳到鳄鱼上
bool canFirstJump(int x, int y, int D) {
    if (abs(x) <= 7.5 && abs(y) <= 7.5) return false;   //鳄鱼在岛内不算,应对测试点3
    return pow(x, 2) + pow(y, 2) <= pow(D + 7.5, 2);
}

//判断是否能从一只鳄鱼跳到另一只鳄鱼上
bool canJump(int x1, int y1, int x2, int y2, int D) {
    if (abs(x2) >= 50 || abs(y2) >= 50) return false;     //鳄鱼在岸上不算,应对测试点3
    return pow(x1-x2, 2) + pow(y1-y2, 2) <= pow(D, 2);
}

//判断是否能从鳄鱼跳到岸上
bool canEscape(int x, int y, int D) {
    return (abs(x) + D >= 50) || (abs(y) + D >= 50);
}