题目 P4568 [JLOI2011]飞行路线 要求找到在最多可以免费乘坐k条航线的情况下,从城市s到城市t的最少花费。这是一个典型的分层图问题。
分层图的建模
1. 建立层次
将原图分成k+1层,表示在0到k次免费乘坐的情况下的状态。第i层表示已经使用了i次免费乘坐机会的状态。
2. 建立节点和边
每层图的节点和原图的节点对应,每层之间通过边相连:
- 同层边: 从第i层的一个节点到同一层的另一个节点,边权保持不变。
- 跨层边: 从第i层的一个节点到下一层(第i+1层)的对应节点,边权设为0,表示使用一次免费乘坐机会。
这样,每个节点(i, v)
表示在使用了i次免费乘坐机会时在节点v。
算法步骤
1. 初始化
创建一个距离数组dist
,dist[i][j]
表示在使用了i次免费机会后到达节点j的最短距离。初始时,所有值设为无穷大,起点的距离为0。
vector<vector<int>> dist(k + 1, vector<int>(n, INF));
dist[0][s] = 0;
2. 优先队列
使用优先队列(最小堆)来存储待处理的节点,每个节点存储当前距离、层次和节点编号。
priority_queue<tuple<int, int, int>, vector<tuple<int, int, int>>, greater<tuple<int, int, int>>> pq;
pq.push({0, 0, s});
3. 处理节点
从优先队列中取出距离最小的节点,对其进行松弛操作:
- 同层松弛: 更新同一层的邻接节点的距离。
- 跨层松弛: 更新下一层的对应节点的距离。
while (!pq.empty()) {
auto [current_distance, current_layer, current_node] = pq.top();
pq.pop();
// 同层松弛
for (const auto& edge : graph[current_node]) {
int next_node = edge.to;
int weight = edge.weight;
if (dist[current_layer][current_node] + weight < dist[current_layer][next_node]) {
dist[current_layer][next_node] = dist[current_layer][current_node] + weight;
pq.push({dist[current_layer][next_node], current_layer, next_node});
}
}
// 跨层松弛
if (current_layer < k) {
for (const auto& edge : graph[current_node]) {
int next_node = edge.to;
if (dist[current_layer][current_node] < dist[current_layer + 1][next_node]) {
dist[current_layer + 1][next_node] = dist[current_layer][current_node];
pq.push({dist[current_layer + 1][next_node], current_layer + 1, next_node});
}
}
}
}
4. 找最小值
在最后的所有层次中找到到达终点t的最小值。
int result = INF;
for (int i = 0; i <= k; ++i) {
result = min(result, dist[i][t]);
}
示例代码
以下是完整的C++代码实现:
#include <iostream>
#include <vector>
#include <queue>
#include <tuple>
#include <limits>
using namespace std;
const int INF = numeric_limits<int>::max();
struct Edge {
int to;
int weight;
};
int main() {
int n, m, k, s, t;
cin >> n >> m >> k >> s >> t;
vector<vector<Edge>> graph(n);
for (int i = 0; i < m; ++i) {
int u, v, w;
cin >> u >> v >> w;
graph[u].push_back({v, w});
graph[v].push_back({u, w});
}
vector<vector<int>> dist(k + 1, vector<int>(n, INF));
dist[0][s] = 0;
priority_queue<tuple<int, int, int>, vector<tuple<int, int, int>>, greater<tuple<int, int, int>>> pq;
pq.push({0, 0, s});
while (!pq.empty()) {
auto [current_distance, current_layer, current_node] = pq.top();
pq.pop();
if (current_distance > dist[current_layer][current_node]) continue;
for (const auto& edge : graph[current_node]) {
int next_node = edge.to;
int weight = edge.weight;
if (dist[current_layer][current_node] + weight < dist[current_layer][next_node]) {
dist[current_layer][next_node] = dist[current_layer][current_node] + weight;
pq.push({dist[current_layer][next_node], current_layer, next_node});
}
}
if (current_layer < k) {
for (const auto& edge : graph[current_node]) {
int next_node = edge.to;
if (dist[current_layer][current_node] < dist[current_layer + 1][next_node]) {
dist[current_layer + 1][next_node] = dist[current_layer][current_node];
pq.push({dist[current_layer + 1][next_node], current_layer + 1, next_node});
}
}
}
}
int result = INF;
for (int i = 0; i <= k; ++i) {
result = min(result, dist[i][t]);
}
cout << result << endl;
return 0;
}
这个代码实现了从起点到终点的最短路径计算,考虑了最多k次的免费乘坐机会.
接下来介绍分层图
什么是分层图Dijkstra算法?
想象一下,你在一个大房子里,每一层楼代表不同的阶段或状态。在这个房子里,你可以从一个房间走到另一个房间,也可以坐电梯到不同的楼层。我们需要找到从一个房间到另一个房间的最短路径,但有一个特别的优惠:你最多可以免费坐几次电梯。
分层图的解释
-
楼层和房间:
- 房子的每一层楼代表一个状态,每层楼有很多房间。
- 你在某一层的某个房间,想要去另一层的另一个房间。
-
路径和电梯:
- 在同一层楼里走路(普通路径),每走一步需要花费时间(边权重)。
- 坐电梯到另一层,可以免费(跨层边)。
分层图Dijkstra算法怎么做?
1. 初始化
首先,我们要记下每个房间到达的最短时间。开始时,我们不知道去哪里需要多少时间,所以把每个房间的时间都设为很大很大的数(无穷大)。但是我们知道从自己开始的房间出发时间是0。
2. 使用优先队列
我们用一个特殊的队列来帮我们记录哪些房间需要处理,先处理最容易到达的房间。这个队列就像一个排序的清单,每次我们从中取出最靠前的房间来处理。
3. 处理房间
每次我们从队列中取出一个房间,然后看看从这个房间出发,能走到哪些其他房间:
- 同层楼房间:如果你在同一层楼走到另一个房间,就更新这个房间的最短时间。
- 跨层电梯:如果你坐电梯到另一层楼的房间,免费,但使用一次免费机会。
4. 找到最短路径
最后,当所有房间都处理完,我们找到最小的时间值,就是从起点到终点的最短路径时间。
示例
假设我们有一个两层的房子,每层楼有三个房间:
-
楼层0:
- 房间0到房间1的时间是2
- 房间0到房间2的时间是4
- 房间1到房间2的时间是1
-
楼层1:
- 房间0到房间1的时间是3
- 房间1到房间2的时间是1
-
跨层电梯:
- 从楼层0的房间0到楼层1的房间1的时间是0(免费)
我们想从楼层0的房间0到楼层1的房间2,最多可以免费坐一次电梯。我们就可以用分层图Dijkstra算法来计算最短时间。
C++代码示例
#include <iostream>
#include <vector>
#include <queue>
#include <tuple>
#include <limits>
using namespace std;
const int INF = numeric_limits<int>::max();
struct Edge {
int to;
int weight;
};
int main() {
int n = 3, m = 5, k = 1, s = 0, t = 2;
vector<vector<Edge>> graph(n);
// 图的边
graph[0].push_back({1, 2});
graph[0].push_back({2, 4});
graph[1].push_back({2, 1});
graph[0].push_back({1, 1}); // 跨层电梯
vector<vector<int>> dist(k + 1, vector<int>(n, INF));
dist[0][s] = 0;
priority_queue<tuple<int, int, int>, vector<tuple<int, int, int>>, greater<tuple<int, int, int>>> pq;
pq.push({0, 0, s});
while (!pq.empty()) {
auto [current_distance, current_layer, current_node] = pq.top();
pq.pop();
if (current_distance > dist[current_layer][current_node]) continue;
for (const auto& edge : graph[current_node]) {
int next_node = edge.to;
int weight = edge.weight;
if (dist[current_layer][current_node] + weight < dist[current_layer][next_node]) {
dist[current_layer][next_node] = dist[current_layer][current_node] + weight;
pq.push({dist[current_layer][next_node], current_layer, next_node});
}
}
if (current_layer < k) {
for (const auto& edge : graph[current_node]) {
int next_node = edge.to;
if (dist[current_layer][current_node] < dist[current_layer + 1][next_node]) {
dist[current_layer + 1][next_node] = dist[current_layer][current_node];
pq.push({dist[current_layer + 1][next_node], current_layer + 1, next_node});
}
}
}
}
int result = INF;
for (int i = 0; i <= k; ++i) {
result = min(result, dist[i][t]);
}
cout << result << endl;
return 0;
}
通过这个例子,你可以看到我们如何一步一步地找到最短路径。这种方法在有很多状态和路径选择时非常有用。希望这个解释能帮助你更好地理解分层图Dijkstra算法!
标签:node,current,layer,dist,JLOI2011,int,P4568,next,路线 From: https://www.cnblogs.com/luyoulingoi/p/18239286