多层图最短路问题

最短路——分层图问题


这里以一道题目为例

题目描述

Alice 和 Bob 现在要乘飞机旅行,他们选择了一家相对便宜的航空公司。该航空公司一共在 \(n\) 个城市设有业务,设这些城市分别标记为 \(0\)\(n-1\),一共有 \(m\) 种航线,每种航线连接两个城市,并且航线有一定的价格。

Alice 和 Bob 现在要从一个城市沿着航线到达另一个城市,途中可以进行转机。航空公司对他们这次旅行也推出优惠,他们可以免费在最多 \(k\) 种航线上搭乘飞机。那么 Alice 和 Bob 这次出行最少花费多少?

输入格式

第一行三个整数 \(n,m,k\),分别表示城市数,航线数和免费乘坐次数。

接下来一行两个整数 \(s,t\),分别表示他们出行的起点城市编号和终点城市编号。

接下来 \(m\) 行,每行三个整数 \(a,b,c\),表示存在一种航线,能从城市 \(a\) 到达城市 \(b\),或从城市 \(b\) 到达城市 \(a\),价格为 \(c\)

输出格式

输出一行一个整数,为最少花费。

样例输入

5 6 1
0 4
0 1 5
1 2 5
2 3 5
3 4 5
2 3 3
0 2 100

样例输出

8

数据规模与约定

对于 \(30\%\) 的数据,\(2 \le n \le 50\)\(1 \le m \le 300\)\(k=0\)

对于 \(50\%\) 的数据,\(2 \le n \le 600\)\(1 \le m \le 6\times10^3\)\(0 \le k \le 1\)

对于 \(100\%\) 的数据,\(2 \le n \le 10^4\)\(1 \le m \le 5\times 10^4\)\(0 \le k \le 10\)\(0\le s,t,a,b < n\)\(a\ne b\)\(0\le c\le 10^3\)

另外存在一组 hack 数据。


区别与一般的单图问题,这道题中我们需要考虑可以免费坐飞机的航线

相当于每个节点都存在一个状态,即到这个节点还能免费坐飞机的次数

拥有相同状态的节点共同组成一张图

所以需要对寻路的方式进行部分修改,这里采用优先队列的Dijkstra最短路算法

struct node
{
int dw, dnode, cnt;
bool operator<(const node& a)const {
return dw > a.dw;
}
};

构造优先队列 priority_queue 使用的结构体,并重载小于号

其中存储了这个节点的编号dnode ,节点到起始点的距离 dw ,以及当前节点使用的免费次数 cnt

int dis[20010][12];
bool vis[20010][12];

同样的,记录最短距离的 dis 和记录访问节点的 vis 数组都需要根据状态的不同来进行分层操作

void dijkstra(int st) {
init();
q.push({ 0, st, 0 });
dis[st][0] = 0;
while (q.size())
{
const node ttop = q.top();
q.pop();
if (vis[ttop.dnode][ttop.cnt]) continue;
vis[ttop.dnode][ttop.cnt] = 1;
for (auto it = e[ttop.dnode].begin(); it != e[ttop.dnode].end(); it++) {
if (ttop.cnt < k&& dis[it->v][ttop.cnt + 1] > dis[ttop.dnode][ttop.cnt]) {
dis[it->v][ttop.cnt + 1] = dis[ttop.dnode][ttop.cnt];
q.push({ dis[it->v][ttop.cnt + 1],it->v,ttop.cnt + 1 });
}
if (dis[it->v][ttop.cnt] > dis[ttop.dnode][ttop.cnt] + it->w) {
dis[it->v][ttop.cnt] = dis[ttop.dnode][ttop.cnt] + it->w;
q.push({ dis[it->v][ttop.cnt],it->v,ttop.cnt });
}
}
}
}

除了松弛操作外,多层图和单图的源码几乎相同,都采用了优先队列贪心来找当前最优的节点

在松弛操作内,我们需要考虑当前的节点能否使用免费的次数,免费后是不是最优

  • 当前的节点可以免费
if (ttop.cnt < k&& dis[it->v][ttop.cnt + 1] > dis[ttop.dnode][ttop.cnt]) {//判断是不是最优
dis[it->v][ttop.cnt + 1] = dis[ttop.dnode][ttop.cnt];//如果能更新最短路径,那就更新
q.push({ dis[it->v][ttop.cnt + 1],it->v,ttop.cnt + 1 });//将更新后的节点放入队列,注意状态也要更新
}
  • 当前的节点不能免费或不采取免费
if (dis[it->v][ttop.cnt] > dis[ttop.dnode][ttop.cnt] + it->w) {//判断能否更新
dis[it->v][ttop.cnt] = dis[ttop.dnode][ttop.cnt] + it->w;
q.push({ dis[it->v][ttop.cnt],it->v,ttop.cnt });//放入更新后的节点
}

Dijkstra寻路结束后,我们遍历一遍终点节点的所有状态,找到一个最小的dis[ed][i],即最优解

int ans = 1e9;
for (int i = 0; i <= k; i++) ans = min(ans, dis[ed][i]);

图上的每一个节点都有向下层的可联通节点前进的无权边(免费)

上图以 \(0\) 号节点为例,它可以通过免费次数构建出来的边向下层图移动,直到免费次数用尽

总而言之,解决分层图的关键就在建图,需要正确地通过不同的状态构造出图

完整代码:

#include <vector>
#include <iostream>
#include <string>
#include <cstring>
#include <algorithm>
#include <queue>
#define streampreset() ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
using namespace std;
struct edge
{
int v, w;
};
struct node
{
int dw, dnode, cnt;
bool operator<(const node& a)const {
return dw > a.dw;
}
};
int n, m, k;
int st, ed;
vector<edge> e[50010];
priority_queue<node> q;
int dis[20010][12];
bool vis[20010][12];
void insert(int u, int v, int w) {
e[u].push_back({ v,w });
}
void init(void) {
memset(dis, 0x3f, sizeof dis);
}
void dijkstra(int st) {
init();
q.push({ 0, st, 0 });
dis[st][0] = 0;
while (q.size())
{
const node ttop = q.top();
q.pop();
if (vis[ttop.dnode][ttop.cnt]) continue;
vis[ttop.dnode][ttop.cnt] = 1;
for (auto it = e[ttop.dnode].begin(); it != e[ttop.dnode].end(); it++) {
if (ttop.cnt < k&& dis[it->v][ttop.cnt + 1] > dis[ttop.dnode][ttop.cnt]) {
dis[it->v][ttop.cnt + 1] = dis[ttop.dnode][ttop.cnt];
q.push({ dis[it->v][ttop.cnt + 1],it->v,ttop.cnt + 1 });
}
if (dis[it->v][ttop.cnt] > dis[ttop.dnode][ttop.cnt] + it->w) {
dis[it->v][ttop.cnt] = dis[ttop.dnode][ttop.cnt] + it->w;
q.push({ dis[it->v][ttop.cnt],it->v,ttop.cnt });
}
}
}
}
int main()
{
streampreset();
cin >> n >> m >> k >> st >> ed;
int u, v, w;
for (int i = 0; i < m; i++) {
cin >> u >> v >> w;
insert(u, v, w);
insert(v, u, w);
}
dijkstra(st);
int ans = 1e9;
for (int i = 0; i <= k; i++) ans = min(ans, dis[ed][i]);
cout << ans;
return 0;
}
posted @   才瓯  阅读(26)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具
点击右上角即可分享
微信分享提示