赞
踩
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.
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 G
1 to G
M.
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.
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
.
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
G1
2.0 3.3
2 1 2 10
1 G1 9
2 G1 20
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上不通过确实很坑,我试了很久才发现,老师在题目中说给出的城市编号在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"); }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。