P4568 [JLOI2011] 飞行路线

题目 P4568 [JLOI2011]飞行路线 要求找到在最多可以免费乘坐k条航线的情况下,从城市s到城市t的最少花费。这是一个典型的分层图问题。

分层图的建模

1. 建立层次

将原图分成k+1层,表示在0到k次免费乘坐的情况下的状态。第i层表示已经使用了i次免费乘坐机会的状态。

2. 建立节点和边

每层图的节点和原图的节点对应,每层之间通过边相连:

  • 同层边: 从第i层的一个节点到同一层的另一个节点,边权保持不变。
  • 跨层边: 从第i层的一个节点到下一层(第i+1层)的对应节点,边权设为0,表示使用一次免费乘坐机会。

这样,每个节点(i, v)表示在使用了i次免费乘坐机会时在节点v。

算法步骤

1. 初始化

创建一个距离数组distdist[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算法?

想象一下,你在一个大房子里,每一层楼代表不同的阶段或状态。在这个房子里,你可以从一个房间走到另一个房间,也可以坐电梯到不同的楼层。我们需要找到从一个房间到另一个房间的最短路径,但有一个特别的优惠:你最多可以免费坐几次电梯。

分层图的解释

  1. 楼层和房间

    • 房子的每一层楼代表一个状态,每层楼有很多房间。
    • 你在某一层的某个房间,想要去另一层的另一个房间。
  2. 路径和电梯

    • 在同一层楼里走路(普通路径),每走一步需要花费时间(边权重)。
    • 坐电梯到另一层,可以免费(跨层边)。

分层图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算法!

posted @ 2024-06-09 09:57  luyouling  阅读(40)  评论(0)    收藏  举报