PTA 1072 Gas Station PAT甲级真题 题目详解 PTA和牛客网两平台测试点不通过分析解决

20 篇文章 2 订阅
9 篇文章 0 订阅

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

这道题花了我好长时间来找错,本身思路倒是不难,但是输入数据实在是太过于恶心。这道题在PTA和牛客网上都有对应题目,两个平台上的测试点不完全相同,所以有可能在牛客网上能通过的代码在PTA上过不了,反之也有可能。

我这里总结了两个平台上不通过常见的几个关键点,也对为何不能通过测试点进行了分析。


A gas station has to be built at such a location that the minimum distance between the station and any of the residential housing is as far away as possible. However it must guarantee that all the houses are in its service range.

Now given the map of the city and several candidate locations for the gas station, you are supposed to give the best recommendation. If there are more than one solution, output the one with the smallest average distance to all the houses. If such a solution is still not unique, output the one with the smallest index number.

Input Specification:

Each input file contains one test case. For each case, the first line contains 4 positive integers: N (≤ 1 0 3 10^3 103), the total number of houses; M (≤10), the total number of the candidate locations for the gas stations; K (≤ 1 0 4 10^4 104), the number of roads connecting the houses and the gas stations; and D S D_S DS, the maximum service range of the gas station. It is hence assumed that all the houses are numbered from 1 to N, and all the candidate locations are numbered from G1 to GM.

Then K lines follow, each describes a road in the format

P1 P2 Dist

where P1 and P2 are the two ends of a road which can be either house numbers or gas station numbers, and Dist is the integer length of the road.

Output Specification:

For each test case, print in the first line the index number of the best location. In the next line, print the minimum and the average distances between the solution and all the houses. The numbers in a line must be separated by a space and be accurate up to 1 decimal place. If the solution does not exist, simply output No Solution.

Sample Input 1:

4 3 11 5
1 2 2
1 4 2
1 G1 4
1 G2 3
2 3 2
2 G2 1
3 4 2
3 G3 2
4 G1 3
G2 G1 1
G3 G2 2

Sample Output 1:

G1
2.0 3.3

Sample Input 2:

2 1 2 10
1 G1 9
2 G1 20

Sample Output 2:

No Solution

题目分析

题意说明

题目要求给定一个城市地图,地图中有很多建造汽油站的候选点,要选出一个候选点位置使得它到所有城市的最小距离最大,如果多个候选点有相同的最小距离,则选择到所有城市的平均距离最小的那个。如果仍然相同,则输出城市编号最小的那个。当然,最重要的,所有城市必须在候选点的服务范围内。

输入输出分析

题目在输入中第一行首先给出城市的数量N和候选点的数量M以及路的数量K和汽油站的最大服务范围。这里,城市的编号名称从1-N(事实上,PTA平台中给出的路径中有的城市编号没有遵循这一项题目要求),候选点编号名称为G1到GN。

后面K行输入分别对应K条路的信息,每一行包括路的两个端点名称(注意既可以是城市也可以是候选点)以及路径长度。

如果能找到满足条件的候选点,则输出该点名称,以及它到所有城市的最小距离和平均距离;否则输出No Solution

关键点分析

这道题一看就可以知道是最短路径问题,因为题目中的候选点数量取值范围最大没有超过10,所以依次对每个候选点依次做一次Dijkstra算法一共也只有10次而已,所以相对Floyd算法更快。

这种城市地图路径问题当然首先还是考虑用邻接表实现比较节省内存,所以我仍然使用的是邻接表。

除了这些,还有几个问题需要考虑:

  • 如何将候选点和各城市结点既能区分又能有效存储在一起?
  • 如何找出最小距离最大,并且如果相同平均距离最小,再相同编号最小的候选点呢?

这些问题我放到了解法分析中进行解释。

解法分析

数据存储

因为在实际执行Dijkstra算法时,需要不区分候选点和实际城市结点,所以候选点和城市结点都应该使用相同的邻接表结构。这里我将候选点的编号直接加N(N为城市结点数)与城市结点一起存储在一个邻接表中。

当然,除此之外还需要Dijkstra算法必须的collected数组和dist距离数组。

具体解法函数说明

我的代码中最主要的解法函数就是solve和checkCandidate。

其中,solve负责对每一个候选点依次调用checkCandidate函数,只要有一次返回结果为true就说明一定有解,之后就可以把需要输出的相关数据输出;否则一定无解。

checkCandidate函数的主要逻辑是将传入的候选点作为源点执行一遍Dijkstra算法,然后计算出该候选点的最小距离和平均距离。如果这个最小距离比系统历史选出的最小距离还小或者相等但平均距离比系统选出的历史平均距离大,那么认为该候选点是当前解,并重新赋值系统最小距离和平均距离。

问题说明

这道题按上述思路实现完后我先在PTA平台上提交,最后一个测试点4没有通过得到段错误,然后在网上查阅资料时发现牛客上也有一模一样的题目,于是在牛客上提交之后又出现新的错误,发现牛客上的测试点输入用例和PTA上给的不同。下面我分别对这个两个平台的测试点为什么不通过进行分析。

牛客上测试点需要注意问题

牛客中给出的测试点用的输入数据非常恶心,主要要考虑以下几点:

(1)输入的路径中可能有G1 G1 10,路径的两端是自己到自己

(2)端点相同的路径可能有多条,它们的长度不同,比如可能同时出现G1 1 10和1 G1 15这两条路,执行算法时需要选择最短的那条路

(3)给出的候选点没有都出现在后面给出的路径中,比如下面这个用例

148 10 54 361
89 18 68
107 G1 41
99 113 83
56 G10 59
99 98 70
108 18 57
146 112 22
68 75 27
3 77 70
67 11 2
21 99 35
88 7 51
106 28 62
94 100 89
104 1 31
36 91 13
140 123 41
54 146 69
76 107 37
140 111 83
29 44 95
147 40 94
72 118 86
26 94 47
81 43 45
73 129 78
94 29 83
26 108 88
27 62 90
106 16 68
61 121 73
76 G2 96
66 141 79
77 76 93
99 138 29
39 128 21
124 66 54
68 G5 61
129 51 15
28 49 32
141 104 67
60 89 86
50 65 19
70 26 71
9 102 55
78 47 71
15 83 96
122 104 15
112 47 22
116 30 42
89 77 100
131 52 95
14 136 1
44 68 27

对应输出应该为:

No Solution

可以看到,候选点有10个,但是所有路径中只出现了G1 G2 G5 G10,其他都没有给出。

(4)有的候选点是孤点,到有的城市没有可达路径

在这里插入图片描述

PTA上测试点不通过原因

PTA上不通过确实很坑,我试了很久才发现,老师在题目中说给出的城市编号在1-N这句话是错误的,因为N最大取值是1000,而候选点数量最大为10,如果设置的结点总数为1010那么就会出现段错误,因为测试点4的输入用例中有的路径端点的编号超出了这个范围。

那么我是怎么发现这个问题的呢?

最初在我现在代码的InsertEdge函数的105行处我是没有做那个判断的。当我将MaxVertexNum值设为1010时会出现段错误,而当我设为9999或者100000甚至更大时就没有这个问题可以通过最后一个测试点了。所以我推测输入中一定有城市编号大于了1000。

为了证明,我在InsertEdge函数编写一个判断,如果插入的边的端点大于了给定的输入城市数,那么就打印hello,这时无论设置MaxVertexNum值为多少,最后一个测试点都无法通过了。因此可以断定,老师给的题目有误导,路径端点的城市编号有可能超过城市数。

因此我在InsertEdge函数中增加了这个判断,如果超出则不考虑这条路径。

很多人在PTA上仍然通过了所有测试点,我猜测是使用了vector等库函数的原因,调用了类似push等操作,避免了严格规定编号的条件;此外也有的人是因为将最大结点数设置的很大通过的

在这里插入图片描述


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

#define INF 65535
#define ERROR -1
/* ——————无向图的邻接表定义开始—————— */
#define MaxVertexNum 1010
typedef int Vertex;
typedef int WeightType;

typedef struct ENode *PtrToENode;
struct ENode{
    char vertex1[4], vertex2[4];
    WeightType dist;  // 路的长度
};
typedef PtrToENode Edge;

typedef struct AdjVNode *PtrToAdjVNode;
struct AdjVNode{
    Vertex AdjV;
    WeightType dist;  // 路的长度
    PtrToAdjVNode Next;
};

typedef struct Vnode{
    PtrToAdjVNode FirstEdge;
} AdjList[MaxVertexNum];

typedef struct GNode *PtrToGNode;
struct GNode{
    int HouseNum;   // the total number of houses
    /* Candidate存储在house结点后面 */
    int Candidate;     // the total number of the candidate locations for the gas stations
    int Ne;
    AdjList G;
};
typedef PtrToGNode LGraph;

LGraph CreateGraph( int HouseNum, int Candidate );
void DestoryGraph( LGraph Graph );
void InsertEdge(LGraph Graph, Edge E);
/* ——————无向图的邻接表定义结束—————— */

int serviceRange;
int solutionIndex;
float minimumDis, averageDis;
bool collected[MaxVertexNum];
WeightType dist[MaxVertexNum];

LGraph BuildGraph();
void init(LGraph Graph);
int FindMinDist(LGraph Graph);
bool checkCandidate(LGraph Graph, int candidateIndex);
void solve(LGraph Graph);

int main()
{
    LGraph graph;
    graph = BuildGraph();
    solve(graph);
    DestoryGraph(graph);
    return 0;
}

LGraph CreateGraph( int HouseNum, int Candidate )
{
    Vertex V;
    LGraph Graph;

    Graph = (LGraph)malloc(sizeof(struct GNode));
    Graph->HouseNum = HouseNum;
    Graph->Candidate = Candidate;
    Graph->Ne = 0;

    for (V = 0; V < (Graph->HouseNum + Graph->Candidate); ++V)
        Graph->G[V].FirstEdge = NULL;

    return Graph;
}

void DestoryGraph( LGraph Graph )
{
    Vertex V;
    PtrToAdjVNode Node;
    for (V = 0; V < (Graph->HouseNum + Graph->Candidate); ++V) {
        while (Graph->G[V].FirstEdge) {
            Node = Graph->G[V].FirstEdge;
            Graph->G[V].FirstEdge = Node->Next;
            free(Node);
        }
    }
    free(Graph);
}

void InsertEdge(LGraph Graph, Edge E)
{
    PtrToAdjVNode NewNode;
    Vertex V1, V2;

    V1 = (E->vertex1[0] == 'G') ? (atoi(E->vertex1 + 1) + Graph->HouseNum) : atoi(E->vertex1);
    V2 = (E->vertex2[0] == 'G') ? (atoi(E->vertex2 + 1) + Graph->HouseNum) : atoi(E->vertex2);
    --V1; --V2; // 编号都是从1开始的
    // 下面这一步必须判断,题目给的数据并没有遵守题意,给出的城市编号有比N大的值,对应最后一个测试点“段错误”,巨坑!!
    if (((E->vertex1[0] != 'G') && atoi(E->vertex1) > Graph->HouseNum) ||
        ((E->vertex2[0] != 'G') && atoi(E->vertex2) > Graph->HouseNum))
        return;

    NewNode = (PtrToAdjVNode)malloc(sizeof(struct AdjVNode));
    NewNode->AdjV = V2; NewNode->dist = E->dist;
    NewNode->Next = Graph->G[V1].FirstEdge;
    Graph->G[V1].FirstEdge = NewNode;

    NewNode = (PtrToAdjVNode)malloc(sizeof(struct AdjVNode));
    NewNode->AdjV = V1; NewNode->dist = E->dist;
    NewNode->Next = Graph->G[V2].FirstEdge;
    Graph->G[V2].FirstEdge = NewNode;
}

LGraph BuildGraph()
{
    LGraph graph;
    Edge E;
    int HouseNum, Candidate, i;

    scanf("%d %d", &HouseNum, &Candidate);
    graph = CreateGraph(HouseNum, Candidate);
    scanf("%d", &(graph->Ne));
    scanf("%d", &serviceRange);
    if (graph->Ne != 0) {
        E = (Edge)malloc(sizeof(struct ENode));
        for (i = 0; i < graph->Ne; ++i) {
            scanf("%s %s %d", E->vertex1, E->vertex2, &(E->dist));
            InsertEdge(graph, E);
        }
        free(E);
    }

    return graph;
}

void init(LGraph Graph)
{
    Vertex V;
    for (V = 0; V < (Graph->HouseNum + Graph->Candidate); ++V) {
        collected[V] = false;
        dist[V] = INF;
    }
}

int FindMinDist(LGraph Graph)
{
    Vertex MinV, V;
    int MinDist = INF;

    for (V = 0; V < (Graph->HouseNum + Graph->Candidate); ++V) {
        if (!collected[V] && dist[V] < MinDist) {
            MinDist = dist[V];
            MinV = V;
        }
    }
    if (MinDist < INF) return MinV;
    else return ERROR;
}

bool checkCandidate(LGraph Graph, int candidateIndex)
{
    Vertex source, V;
    PtrToAdjVNode edge;
    double sum, minDis, average;

    source = Graph->HouseNum + candidateIndex;
    if (!Graph->G[source].FirstEdge) return false;
    init(Graph);
    // 先将源点放入已取元素的集合中,然后更改其邻接点相关值
    collected[source] = true; dist[source] = 0;
    for (edge = Graph->G[source].FirstEdge; edge; edge = edge->Next) {
        if (edge->dist < dist[edge->AdjV])
            dist[edge->AdjV] = edge->dist;
    }
    // 然后依次从未取元素中找距离最小的元素放入集合中
    while (true) {
        V = FindMinDist(Graph);
        if (dist[V] > serviceRange && V < Graph->HouseNum)
            return false;
        if (V == ERROR)
            break;
        collected[V] = true;
        for (edge = Graph->G[V].FirstEdge; edge; edge = edge->Next) {
            if (!collected[edge->AdjV] && (dist[V] + edge->dist < dist[edge->AdjV])) {
                dist[edge->AdjV] = dist[V] + edge->dist;
            }
        }
    }
    minDis = INF; sum = 0;
    for (V = 0; V < Graph->HouseNum; ++V) {
        if (dist[V] == INF) return false;
        if (dist[V] < minDis)
            minDis = dist[V];
        sum += dist[V];
    }
    average = sum / Graph->HouseNum;
    if (minDis > minimumDis || (minDis == minimumDis && average < averageDis)) {
        solutionIndex = candidateIndex + 1;
        minimumDis = minDis;
        averageDis = average;
    }
    return true;
}

void solve(LGraph Graph)
{
    int i;
    bool flag = false;
    minimumDis = 0;
    averageDis = INF;
    for (i = 0; i < Graph->Candidate; ++i) {
        if (checkCandidate(Graph, i))
            flag = true;
    }
    if (flag) printf("G%d\n%.1lf %.1lf\n", solutionIndex, minimumDis, averageDis);
    else printf("No Solution\n");
}

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值