【PAT甲级真题】- All Roads Lead to Rome (30)

题目来源

All Roads Lead to Rome (30)

注意点:

  • 输出 happy 值最大的
  • happy 一样输出平均 happy 最大的

题目描述

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.

输入描述:

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.

输出描述:

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 recommended. 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 recommended route. Then in the next line, you are supposed to print the route in the format "City1->City2->...->ROM".

输入例子:

复制代码
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

输出例子:

复制代码
3 3 195 97
HZH->PRS->ROM

思路简介

dij + 保存父节点深搜

题中最短路就是 dij 的模板

但是后续要遍历所有的最短路求价值最大,必须要能够保存所有的最短路

一种常规的操作是保存每个节点的最短路父节点,然后从终点开始深搜

深搜的时候记录最小深度来代替平均值的计算

博主用的链式前向星建图,非常建议大家使用链式前向星,我后续会写相关的文章。。。大家可以自己学习一下。。。

为什么不保存子节点然后从起点深搜呢?

因为保存父节点的话,当最短路更新的时候,就直接清空当前结点的父节点数组即可,先前的父节点不是最短路

而且最短路的更新方程是 for(u):dis[v]=max(dis[v],dis[u]+w),是对父节点的遍历,保存父节点也符合更新的逻辑

但是保存子结点的话,最短路更新的时候,必须要删掉其中的部分子结点,同时可能当前结点也不是最短路的一部分,调整起来很麻烦

遇到的问题

一遍过,但是调试了很久,说说调的时候的问题

输入建图时:

  • 插入两次,是无向图

dij 方面:

  • 在距离相等时插入父节点,要判重,会出现插入相同父节点的情况
  • 用 pair 存边

深搜方面:

  • 更新价值的最大值时,同时要更新最小的深度,因为最小深度记录的是在价值最大时的最小深度

题目方面

  • 初始点不算在输入结点当中
  • 初始点没有价值,记为 0 即可
  • 输出平均值时不用小数部分
  • 路径的深度不算起点
  • 输出 path 时反向输出,因为是从终点开始搜索的

代码

cpp 复制代码
/**
 * https://www.nowcoder.com/pat/5/problem/4315
 * 状态保存dij
 */
#include<bits/stdc++.h>
using namespace std;
typedef pair<int,int> pii;

map<string,int>mp;
map<int,string>mps;

const int N=2e5;
int head[N],cnt=0,happy[N];
struct Edge{
    int next,to,k;

    Edge():next(0),to(0),k(0){};
    Edge(int next,int to,int k):next(next),to(to),k(k){}
}e[N];

void add(int u,int v,int k){
    e[cnt]=Edge(head[u],v,k);
    head[u]=cnt++;
}

int dis[N];
vector<int> pre[N];//记录父结点,深搜
void dij(int s){
    priority_queue<pii,vector<pii>,greater<pii>> pq;//pair选择dis最小的点
    pq.push({0,s});
    dis[s]=0;

    while(!pq.empty()){
        int d=pq.top().first;
        int u=pq.top().second;
        pq.pop();

        if(d>dis[u])continue;//u已经被更新过了,这个记录无效

        for(int i=head[u];i!=-1;i=e[i].next){//遍历 u 的边
            int v=e[i].to;

            if(dis[u]+e[i].k<dis[v]){
                dis[v]=dis[u]+e[i].k;
                pq.push({dis[v],v});

                //更新父结点,先前的父节点不是最短路了
                pre[v].clear();
                pre[v].emplace_back(u);
                continue;
            }

            if(dis[u]+e[i].k==dis[v]){//距离相同,插入父节点
                if(find(pre[v].begin(),pre[v].end(),u)==pre[v].end())//记得判重
                    pre[v].emplace_back(u);
            }
        }
    }
}

int max_happy=0,min_deep=0x3f3f3f3f,diffnum=0;
vector<int> path;
void dfs(int u,int start,int sum,int deep,vector<int>res){
    res.emplace_back(u);

    if(u==start){
        diffnum++;
        if(max_happy<sum){
            max_happy=sum;
            min_deep=deep;//更新最小深度
            path=res;
            return;
        }

        if(max_happy==sum&&min_deep>deep){
            min_deep=deep;
            path=res;
        }

        return;
    }

    for(auto v:pre[u]){
        dfs(v,start,sum+happy[v],deep+1,res);
    }
}

void init(){
    memset(head,-1,sizeof(head));
    memset(dis,0x3f3f3f3f,sizeof(dis));
}

void solve(){
    int n,m;
    string start_city;
    cin>>n>>m>>start_city;

    mp[start_city]=0,happy[0]=0,mps[0]=start_city;

    //建图
    for(int i=1;i<n;++i){
        string c;
        int h;
        cin>>c>>h;
        mp[c]=i,happy[i]=h,mps[i]=c;
    }

    
    for(int i=0;i<m;++i){
        string u,v;
        int k;
        cin>>u>>v>>k;
        //注意建无向图,插入两次
        add(mp[u],mp[v],k);
        add(mp[v],mp[u],k);
    }

    //dij
    dij(mp[start_city]);
    vector<int>res;
    dfs(mp["ROM"],mp[start_city],happy[mp["ROM"]],1,res);

    int len=path.size();
    cout<<diffnum<<' '<<dis[mp["ROM"]]<<' '<<max_happy<<' '<<max_happy/(min_deep-1)<<'\n';
    for(int i=len-1;i>=0;i--){
        cout<<mps[path[i]];
        if(i)cout<<"->";
    }
}

int main(){
    ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
    //fstream in("in.txt",ios::in);cin.rdbuf(in.rdbuf());

    init();

    int T=1;
    //cin>>T;
    while(T--){
        solve();
    }
    return 0;
}
相关推荐
PD我是你的真爱粉5 小时前
Redis 数据类型与底层实现:从 SDS、Quicklist 到 ZSet 跳表彻底讲透
数据结构·redis
Cando学算法5 小时前
双指针之快慢指针
算法
汀、人工智能5 小时前
[特殊字符] 第100课:任务调度器
数据结构·算法·数据库架构·贪心··任务调度器
每日任务(希望进OD版)5 小时前
二分法刷题
算法·二分
John.Lewis5 小时前
C++进阶(6)C++11(2)
开发语言·c++·笔记
Mapleay5 小时前
CLion IDE 使用
c++
兵哥工控5 小时前
MFC用高精度计时器实现五段时序控制的改进
c++·mfc
会编程的土豆6 小时前
日常做题 vlog
数据结构·c++·算法
22信通小白6 小时前
USRP初学者使用手册(基础配置及bug记录)——Linux+Clion(单台X310收发)
linux·运维·c++·5g·bug·信息与通信