天梯赛 L3-040 人生就像一场旅行 题解 cpp

L3-040 人生就像一场旅行 题解

题目分析

给定一个带权无向图,每个边包含旅费w和心情值g。要求从指定起点出发,找到所有满足总旅费不超过预算b的路径,并按以下规则输出:

  1. 第一行输出所有可达城市(按编号升序)
  2. 第二行输出所有可达城市中总心情值最高的城市(按编号升序)

约束条件

  • 到达每座城市的路线都是最便宜路线,若有多个同样便宜的路线可以到达该城市,则选择获得心情指数最高路线。
  • 城市编号从1开始

算法思路

第一眼很明显需要求源点到其余各点的最短路径(只不过最短路径变成了最少花费),那肯定首选迪杰斯特拉算法(!!! 一定要堆优化不然大概率超时 有问题评论一下我专门做篇文章讲一下)。不过在计算最少话费过程中还需要知道到哪个城市总路线中获取心情指数最高,我们只需要再用一个mood数组在更新到达各城市花费时更新mood数组.

Dijkstra算法变种

  1. 优先队列优化
    使用优先队列处理节点,按以下规则排序:

    • 总旅费较小的路径优先
    • 旅费相同时,总心情值较大的路径优先
  2. 双数组维护状态

    • dis[]:记录到达各城市的最小总旅费
    • mood[]:记录在最小旅费下的最大总心情值
  3. 动态更新策略

    • 当发现更小旅费时,更新旅费和心情值
    • 当旅费相同但心情值更大时,仅更新心情值

代码实现

#include<bits/stdc++.h>
#define endl "\n";
#define ll long long
#define all(rq) rq.begin(),rq.end()

using namespace std;
const int INF=0x3f3f3f3f;

class edge {
public:
    int end, w, g; //end为该边到达的终点 w为该边的花费 g为该边可获得心情指数
    edge(int end=0, int w=0, int g=0) : end(end), w(w), g(g) {}
    
    
    bool operator<(const edge& other) const {
        if(w == other.w) return g < other.g; 
        return w > other.w; // 小顶堆
    }
};

void dij(vector<vector<edge>> &graph, int n, int st, int b) {
    vector<int> dis(n+1, INF), mood(n+1, 0);
    dis[st] = 0;
    
    priority_queue<edge> pq; // 优先队列排序规则:旅费小优先,旅费相同则心情值大优先
    pq.push(edge(st, 0, 0)); // 初始状态:起点旅费0,心情0

    while(!pq.empty()) {
        auto [e, w, g] = pq.top(); // 当前节点信息
        pq.pop();
        
        if(w > dis[e]) continue; // 如果我从源点到达点e的花费w比已记录dis[e]还要大 那么根本没必要更新

        for(auto& [e0, w0, g0] : graph[e]) { // 遍历点e的邻接节点
            int new_cost = w + w0;
            int new_mood = g + g0;
            
            if(new_cost > b) continue; // 超出预算直接跳过

            // 发现更优路径
            if(new_cost < dis[e0]) { //如果经过e到达邻接点e0的花费 比已记录到达点e0的花费dis[e0]还要小 更新dis[e0]和mood 并将该边压入优先队列
                dis[e0] = new_cost;
                mood[e0] = new_mood;
                pq.push(edge(e0, new_cost, new_mood));
            } 
           
            else if(new_cost == dis[e0] && new_mood > mood[e0]) {  // 旅费相同则选择心情更好的
                mood[e0] = new_mood;
                pq.push(edge(e0, new_cost, new_mood));
            }
        }
    }

    // 结果收集
    vector<int> ans1, ans2;
    int max_mood = -1;
    for(int i=1; i<=n; ++i) {
        if(i != st && dis[i] <= b) { //选择不是源点且可以到达的城市(即到达该城市的花费比b小)
            ans1.push_back(i);
            if(mood[i] > max_mood) {
                max_mood = mood[i];
                ans2 = {i};
            } else if(mood[i] == max_mood) {
                ans2.push_back(i);
            }
        }
    }

    // 输出处理
    if(ans1.empty()) {
        cout << "T_T" << endl;
    } else {
        sort(all(ans1)); //升序处理
        sort(all(ans2));
        for(int i=0; i<ans1.size(); ++i) 
            cout << ans1[i] << " \n"[i==ans1.size()-1];
        for(int i=0; i<ans2.size(); ++i)
            cout << ans2[i] << " \n"[i==ans2.size()-1];
    }
}

int main() {
    ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
    int b, n, m, k;
    cin >> b >> n >> m >> k;
    vector<vector<edge>> graph(n+1);
    
    // 建图
    for(int i=0; i<m; ++i) {
        int u, v, w, g;
        cin >> u >> v >> w >> g;
        graph[u].emplace_back(v, w, g); //双向联通所以得是无向边 即需要添加方向相反的两条边
        graph[v].emplace_back(u, w, g);
    }
    
    // 处理查询
    while(k--) {
        int st;
        cin >> st;
        dij(graph, n, st, b);
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值