PTA 1087 All Roads Lead to Rome PAT甲级真题 题目解析

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

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

其实题目倒是不难,思路也很简单,但是为了练习各种数据结构,我没有使用stl,而是自己从头定义了无向图的邻接表、堆以及哈希表结构,所以代码相对较长。


Indeed there are many different tourist routes from our city to Rome. You are supposed to find your clients the route with the least cost while gaining the most happiness.

Input Specification:

Each input file contains one test case. For each case, the first line contains 2 positive integers N (2≤N≤200), the number of cities, and K, the total number of routes between pairs of cities; followed by the name of the starting city. The next N−1 lines each gives the name of a city and an integer that represents the happiness one can gain from that city, except the starting city. Then K lines follow, each describes a route between two cities in the format City1 City2 Cost. Here the name of a city is a string of 3 capital English letters, and the destination is always ROM which represents Rome.

Output Specification:

For each test case, we are supposed to find the route with the least cost. If such a route is not unique, the one with the maximum happiness will be recommanded. If such a route is still not unique, then we output the one with the maximum average happiness – it is guaranteed by the judge that such a solution exists and is unique.

Hence in the first line of output, you must print 4 numbers: the number of different routes with the least cost, the cost, the happiness, and the average happiness (take the integer part only) of the recommanded route. Then in the next line, you are supposed to print the route in the format City1->City2->...->ROM.

Sample Input:

6 7 HZH
ROM 100
PKN 40
GDN 55
PRS 95
BLN 80
ROM GDN 1
BLN ROM 1
HZH PKN 1
PRS ROM 2
BLN HZH 2
PKN GDN 1
HZH PRS 1

Sample Output:

3 3 195 97
HZH->PRS->ROM

题意分析

给定一个城市地图,要求得到达ROM城市的花费最少且收获的快乐最多的路线。

输入分析

第一行首先给出城市数N、路径数K和起始城市名称。

接下来N-1行分别给出除了起始城市外的所有城市名称和各自城市对应的快乐值。起始城市没有快乐值。

然后接下来K行分别给出每条路径信息,包括路径两端城市名以及该路径的花费。

输出分析

要求输出使用最短花费的路径数量、最短花费值、经过该条路径收获的总快乐值、该条路径上的平均快乐值。

之后再按照城市名称顺序输出找到的那条路径。

解法分析

这道题算是我做过的求单源带权图的最短路径算法中,条件最多,考察最全面的一道题,但是其根本思想还是可以套用Dijkstra算法解决。

我做的时候从头实现了用到的三个基本数据结构:

  • 哈希表:主要用来实现从城市名称到对应编号的映射,主要用在添加每一条路径边时,找到路径两端点城市对应的编号从而正确快速的插入边信息
  • 图的邻接表:存储了整个图的信息。其中表头结点数组中的每一个表头结点保存了该结点对应的城市名称和快乐值
  • 堆:这个堆结构主要用于在执行Dijkstra算法搜索最小cost[i]时使用,虽然所有城市数量最大只有200,这里直接遍历搜索速度也不会相差太多,但是为了练习我还是使用了堆结构

代码中大部分主要是上述几种数据结构的定义和相关操作函数的实现。与问题本身相关的实现从代码的338行开始到结尾。有关Dijkstra算法的实现已经是老生常谈,我这里只做了微调,就不赘述了。

当时在PTA平台上一遍就过了,但是在牛客上总是有两个测试点错误,找了很久才发现我这里的堆实现,有可能会重复加入以某个城市为端点的多条边,虽然因为每次只取最小的并不影响实际问题,但是堆的容量可能会超出城市结点数。而我最初创建堆时传入的最大容量仅为城市结点数,改为平方即解决这个问题。


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

#define MaxVertexNum 200
#define INF 65535
/* ——————散列表HashTable定义开始—————— */
#define MAXTABLESIZE 100000 /* 允许开辟的最大散列表长度 */
#define STRLENGTH 3
typedef char KeyType[STRLENGTH + 1];
typedef int ValueType;
typedef int Index;
typedef struct LNode *PtrToLNode;
struct LNode {
    KeyType Key;
    ValueType Value;
    PtrToLNode Next;
};
typedef PtrToLNode Pair;

typedef struct HNode *PtrToHNode;
struct HNode {
    PtrToLNode FirstData;
};
typedef PtrToHNode List;

typedef struct TblNode *HashTable;
struct TblNode {
    int TableSize;
    List Heads;
};
int NextPrime( int N );     // 求比N大的下一个素数
Index Hash(const char *Key, int TableSize); // 字符串关键字的哈希函数

HashTable CreateTable( int TableSize );
void DestroyTable( HashTable H );
Pair Find( HashTable H, KeyType Key );
bool Insert( HashTable H, KeyType Key, ValueType Value );

/* ——————散列表HashTable定义结束—————— */
/* ——————无向图的邻接表定义开始—————— */
typedef int Vertex;
typedef int WeightType;
typedef char NameType[STRLENGTH + 1];
typedef int DataType;

typedef struct ENode *PtrToENode;
struct ENode{
    Vertex V1, V2;
    WeightType cost;  // 路的长度
};
typedef PtrToENode Edge;

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

typedef struct Vnode{
    NameType name;
    DataType happiness;
    PtrToAdjVNode FirstEdge;
} AdjList[MaxVertexNum];

typedef struct GNode *PtrToGNode;
struct GNode{
    int Nv;
    int Ne;
    AdjList G;
};
typedef PtrToGNode LGraph;

LGraph CreateGraph( int VertexNum );
void DestoryGraph( LGraph Graph );
void InsertEdge(LGraph Graph, Edge E);
/* ——————无向图的邻接表定义结束—————— */
/* ——————堆定义开始—————— */
#define ERROR -1
typedef struct ItemNode *PtrToItemNode;
struct ItemNode {
    Vertex index;
    WeightType cost;
};
typedef PtrToItemNode ElementType;
int comp(PtrToItemNode a, PtrToItemNode b);

typedef struct HeapNode *Heap;
struct HeapNode {
    ElementType *Data;
    int Size;
    int Capacity;
};
typedef Heap MinHeap;

MinHeap CreateHeap(int MaxSize);
void DestoryHeap(MinHeap heap);
bool IsFull( MinHeap H );
bool IsEmpty( MinHeap H );
bool InsertNode( MinHeap H, Vertex index, WeightType cost );
Vertex DeleteMin( MinHeap H );
/* ——————堆定义结束—————— */
bool collected[MaxVertexNum];
WeightType cost[MaxVertexNum];
int routeNum[MaxVertexNum];     // 最短路径数量
DataType happiness[MaxVertexNum];
int cityNum[MaxVertexNum];      // 最短路径上途径的城市数
Vertex path[MaxVertexNum];  // 所在最短路径的上一个城市编号

void ReadInput(HashTable hTable, LGraph graph);
void init(LGraph graph);
void solve(HashTable hTable, LGraph graph);     // 使用Dijkstra算法计算
void PrintAns(HashTable hTable, LGraph graph, Vertex destination);

int main()
{
    int N;
    HashTable hTable;
    LGraph graph;
    scanf("%d", &N);

    hTable = CreateTable(N);
    graph = CreateGraph(N);
    ReadInput(hTable, graph);
    solve(hTable, graph);
    DestroyTable(hTable);
    DestoryGraph(graph);
    return 0;
}

/* ——————相关数据结构函数实现开始—————— */
int NextPrime( int N )
{
    int i, p;

    if (N < 2) return 2;
    else p = (N % 2) ? N + 2 : N + 1;
    while( p <= MAXTABLESIZE ) {
        for( i = (int)sqrt(p); i > 1; --i )
            if ( !(p % i) ) break;
        if ( i == 1 ) break;
        else  p += 2;
    }
    return p;
}

Index Hash(const char *Key, int TableSize)
{
    unsigned int h = 0;
    while (*Key != '\0')
        h = (h << 5) + *Key++;
    return h % TableSize;
}

HashTable CreateTable( int TableSize )
{
    HashTable H;
    int i;

    H = (HashTable)malloc(sizeof(struct TblNode));
    H->TableSize = NextPrime(TableSize);

    H->Heads = (List)malloc(H->TableSize * sizeof(struct HNode));
    for( i=0; i<H->TableSize; i++ )
        H->Heads[i].FirstData = NULL;

    return H;
}

void DestroyTable( HashTable H )
{
    int i;
    Pair P, Tmp;

    for( i = 0; i < H->TableSize; ++i ) {
        P = H->Heads[i].FirstData;
        while( P ) {
            Tmp = P->Next;
            free( P );
            P = Tmp;
        }
    }
    free( H->Heads );
    free( H );
}

Pair Find( HashTable H, KeyType Key )
{
    Pair P;
    Index Pos;

    Pos = Hash( Key, H->TableSize );
    P = H->Heads[Pos].FirstData;
    while( P && strcmp(P->Key, Key) )
        P = P->Next;

    return P;
}

bool Insert( HashTable H, KeyType Key, ValueType Value )
{
    Pair P, NewCell;
    Index Pos;

    P = Find( H, Key );
    if ( !P ) {
        NewCell = (Pair)malloc(sizeof(struct LNode));
        strcpy(NewCell->Key, Key);
        NewCell->Value = Value;
        Pos = Hash( Key, H->TableSize );
        NewCell->Next = H->Heads[Pos].FirstData;
        H->Heads[Pos].FirstData = NewCell;
        return true;
    }
    else return false;
}

LGraph CreateGraph( int VertexNum )
{
    Vertex V;
    LGraph Graph;

    Graph = (LGraph)malloc(sizeof(struct GNode));
    Graph->Nv = VertexNum;
    Graph->Ne = 0;

    for (V = 0; V < VertexNum; ++V) {
        Graph->G[V].name[0] = '\0';
        Graph->G[V].FirstEdge = NULL;
    }

    return Graph;
}

void DestoryGraph( LGraph Graph )
{
    Vertex V;
    PtrToAdjVNode Node;
    for (V = 0; V < Graph->Nv; ++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;

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

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

int comp(PtrToItemNode a, PtrToItemNode b)
{
    return (a->cost - b->cost);
}

MinHeap CreateHeap(int MaxSize)
{
    ElementType guard = (ElementType)malloc(sizeof(struct ItemNode));
    MinHeap H = (MinHeap)malloc(sizeof(struct HeapNode));
    guard->index = -1; guard->cost = -1;
    H->Data = (ElementType *)malloc((MaxSize + 1) * sizeof(ElementType));
    H->Size = 0;
    H->Capacity = MaxSize;
    H->Data[0] = guard;
    return H;
}

void DestoryHeap(MinHeap heap)
{
    int i;
    for (i = 0; i <= heap->Size; ++i)
        free(heap->Data[i]);
    free(heap->Data);
    free(heap);
}

bool IsFull( MinHeap H )
{
    return (H->Size == H->Capacity);
}

bool IsEmpty( MinHeap H )
{
    return (H->Size == 0);
}

bool InsertNode( MinHeap H, Vertex index, WeightType cost )
{
    int i;
    PtrToItemNode NewItem;
    if (IsFull(H)) return false;
    i = ++H->Size;
    for (; H->Data[i / 2]->cost > cost; i /= 2)
        H->Data[i] = H->Data[i / 2];
    NewItem = (PtrToItemNode)malloc(sizeof(struct ItemNode));
    NewItem->index = index; NewItem->cost = cost;
    H->Data[i] = NewItem;
    return true;
}

Vertex DeleteMin( MinHeap H )
{
    int Parent, Child;
    Vertex minV;
    ElementType X;
    if (IsEmpty(H)) return ERROR;
    minV = H->Data[1]->index;
    free(H->Data[1]);
    X = H->Data[H->Size--];
    for (Parent = 1; Parent * 2 <= H->Size; Parent = Child) {
        Child = Parent * 2;
        if ((Child != H->Size) && comp(H->Data[Child], H->Data[Child + 1]) > 0)
            ++Child;
        if (comp(X, H->Data[Child]) < 0) break;
        else H->Data[Parent] = H->Data[Child];
    }
    H->Data[Parent] = X;
    return minV;
}
/* ——————相关数据结构函数实现结束—————— */

void ReadInput(HashTable hTable, LGraph graph)
{
    int i;
    char city1[STRLENGTH + 1], city2[STRLENGTH + 1];
    Edge edge; Pair pos1, pos2;
    scanf("%d %s", &(graph->Ne), graph->G[0].name);
    /* 初始化起始城市结点 */
    graph->G[0].happiness = 0;
    Insert(hTable, graph->G[0].name, 0);
    /* 读入并初始化各城市结点 */
    for (i = 1; i < graph->Nv; ++i) {
        scanf("%s %d", graph->G[i].name, &(graph->G[i].happiness));
        Insert(hTable, graph->G[i].name, i);
    }
    /* 读入所有边 */
    edge = (Edge)malloc(sizeof(struct ENode));
    for (i = 0; i < graph->Ne; ++i) {
        scanf("%s %s %d", city1, city2, &(edge->cost));
        pos1 = Find(hTable, city1); pos2 = Find(hTable, city2);
        if (!pos1 || !pos2) continue;
        edge->V1 = pos1->Value; edge->V2 = pos2->Value;
        InsertEdge(graph, edge);
    }
    free(edge);
}

void init(LGraph graph)
{
    Vertex V;
    for (V = 0; V < graph->Nv; ++V) {
        collected[V] = false;
        cost[V] = INF;
        routeNum[V] = 0;
        happiness[V] = graph->G[V].happiness;
        cityNum[V] = 0;
        path[V] = -1;
    }
}

void solve(HashTable hTable, LGraph graph)
{
    PtrToAdjVNode edge;
    MinHeap heap;
    Vertex V, destination;

    init(graph);
    heap = CreateHeap(graph->Nv * graph->Nv);
    // 先将源点放入已取元素的集合中,然后更改其邻接点相关值
    collected[0] = true; cost[0] = 0; routeNum[0] = 1;
    for (edge = graph->G[0].FirstEdge; edge; edge = edge->Next) {
        if (edge->cost < cost[edge->AdjV]) {    // 防止两个城市间有多条边
            cost[edge->AdjV] = edge->cost;
            routeNum[edge->AdjV] += routeNum[0];
            ++cityNum[edge->AdjV];
            path[edge->AdjV] = 0;
            InsertNode(heap, edge->AdjV, edge->cost);
        }
    }
    // 然后依次从未取元素中找距离最小的元素放入集合中,并更改其邻接点相关值
    destination = Find(hTable, "ROM")->Value;   // 找到ROM对应的编号
    while (true) {
        V = DeleteMin(heap);
        if (V == ERROR || V == destination) break;
        if (collected[V]) continue;
        collected[V] = true;
        for (edge = graph->G[V].FirstEdge; edge; edge= edge->Next) {
            if (collected[edge->AdjV]) continue;
            if (cost[V] + edge->cost < cost[edge->AdjV]) {  // 找最小的cost
                cost[edge->AdjV] = cost[V] + edge->cost;
                routeNum[edge->AdjV] = routeNum[V];
                happiness[edge->AdjV] = happiness[V] + graph->G[edge->AdjV].happiness;
                cityNum[edge->AdjV] = cityNum[V] + 1;
                path[edge->AdjV] = V;
                InsertNode(heap, edge->AdjV, cost[edge->AdjV]);
            }
            else if (cost[V] + edge->cost == cost[edge->AdjV]) {    // cost相同
                routeNum[edge->AdjV] += routeNum[V];
                if (happiness[V] + graph->G[edge->AdjV].happiness > happiness[edge->AdjV]) { // 找最大happiness
                    happiness[edge->AdjV] = happiness[V] + graph->G[edge->AdjV].happiness;
                    cityNum[edge->AdjV] = cityNum[V] + 1;
                    path[edge->AdjV] = V;
                }
                else if (happiness[V] + graph->G[edge->AdjV].happiness == happiness[edge->AdjV]) {  // happiness相同
                    if (cityNum[V] + 1 < cityNum[edge->AdjV]) {  // 找最大 average happiness
                        cityNum[edge->AdjV] = cityNum[V] + 1;
                        path[edge->AdjV] = V;
                    }
                }
            }
        }
    }
    DestoryHeap(heap);
    PrintAns(hTable, graph, destination);
}

void PrintAns(HashTable hTable, LGraph graph, Vertex destination)
{
    int n, i;
    Vertex V, arr[MaxVertexNum];
    V = path[destination];
    for (n = 0; V != -1; V = path[V]) {
        arr[n++] = V;
    }
    printf("%d %d %d %d\n", routeNum[destination], cost[destination], happiness[destination], happiness[destination] / cityNum[destination]);
    for (i = n - 1; i >= 0; --i)
        printf("%s->", graph->G[arr[i]].name);
    printf("ROM\n");
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值