L3-040 人生就像一场旅行 题解
题目分析
给定一个带权无向图,每个边包含旅费w
和心情值g
。要求从指定起点出发,找到所有满足总旅费不超过预算b
的路径,并按以下规则输出:
- 第一行输出所有可达城市(按编号升序)
- 第二行输出所有可达城市中总心情值最高的城市(按编号升序)
约束条件:
- 到达每座城市的路线都是最便宜路线,若有多个同样便宜的路线可以到达该城市,则选择获得心情指数最高路线。
- 城市编号从1开始
算法思路
第一眼很明显需要求源点到其余各点的最短路径(只不过最短路径变成了最少花费),那肯定首选迪杰斯特拉算法(!!! 一定要堆优化不然大概率超时 有问题评论一下我专门做篇文章讲一下)。不过在计算最少话费过程中还需要知道到哪个城市总路线中获取心情指数最高,我们只需要再用一个mood数组在更新到达各城市花费时更新mood数组.
Dijkstra算法变种
-
优先队列优化
使用优先队列处理节点,按以下规则排序:- 总旅费较小的路径优先
- 旅费相同时,总心情值较大的路径优先
-
双数组维护状态
dis[]
:记录到达各城市的最小总旅费mood[]
:记录在最小旅费下的最大总心情值
-
动态更新策略
- 当发现更小旅费时,更新旅费和心情值
- 当旅费相同但心情值更大时,仅更新心情值
代码实现
#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;
}