蓝桥杯·全国青少年信息素养大赛·电子学会C/C++考级:分层图最短路解析

引言

刷题的同学一定见过这类"套路题":给你一张图,你手里还有几张"免费通行券",用掉一张就能让某一条路不花代价地走过去,问从起点到终点最少要多少代价。

这种"还能免费用 k 次"的限制,正是 分层图最短路(Layered Graph Shortest Path) 的典型场景。它把"已经用过几张券"变成一个状态维度,套在普通最短路外面,是蓝桥杯、全国青少年信息素养大赛算法赛项里反复出现的高频模型。本文用一道原创题带你吃透它,并给出 C++ / Python 双版参考解。

赛事锚点:第十八届蓝桥杯软件赛报名已开启(2026-10-08 10:00---12-18 14:00,省赛 2027-04-10,C/C++·Python 程序设计 9:00--13:00);中国电子学会 2026 年 12 月软件编程(C/C++ 一级至十级、Python 一至六级)考级报名 9/23--12/2、统考 12-12;全国青少年信息素养大赛 2026---2027 赛季算法应用主题赛同样覆盖图论最短路。

题目 / 项目目标

【校园科技节摆渡车】

校园里有 n 个站点、m 条双向道路,每条道路有一个通行时间 w。你手上有 k 张"瞬移券",使用一张可以让任意一条 道路的通行时间变为 0(相当于免费走一次)。每张券只能用一次,且全局共享这 k 张。

求从 1 号站点到 n 号站点的最短通行时间 ;如果怎么走都到不了,输出 -1。

输入格式

  • 第一行 n m k(站点数、道路数、瞬移券数量)

  • 接下来 m 行,每行 u v w(站点 u、v 之间有一条双向路,通行时间 w)

输出格式

  • 一个整数:最短通行时间;不可达输出 -1

样例输入

复制代码
4 5 1
1 2 2
2 3 3
3 4 2
1 3 10
2 4 10

样例输出

复制代码
2

样例说明 :用 1 张券免掉道路 1--3 的代价(原 10 → 0),走 1→(免费)3→4 = 0 + 2 = 2,比不用券的 1→2→3→4 = 7 更优。

核心考点

  1. 最短路算法(Dijkstra):边权非负,用堆优化 Dijkstra 即可。
  2. 状态维度建模(分层图的灵魂) :把"已用券数"变成额外一维。原图复制 k+1 份,第 i 层表示"已经用了 i 张券"。
  3. 层内边 vs 跨层边:
  4. 层内边:在某一层里照原图正常连,边权不变;
  5. 跨层边:从第 i 层走到第 i+1 层时,把这条边"免费"连过去,边权为 0。
  6. 终点取各层最小值 :不一定要用满 k 张券,所以终点是 k+1 层里 n 号点的最小值。

解法 / 拆解

第一步:把图"竖着"复制 k+1 层

设原图有 n 个点。我们构造一个新图,点编号写作 u + layer * n,其中:

  • u 是原站点编号(0-based,方便计算);

  • layer 是"已用券数",取值 0, 1, ..., k,共 k+1 层。

新图总点数 = (k+1) * n。

第二步:连两种边

对原图的每条边 (u, v, w):

  1. 层内边 (每一层都连,保持正常通行):
    第 layer 层:u+layer*n ←→ v+layer*n 权值 w
  2. 跨层边 (用掉一张券,条件 layer < k):
    u+layer*n → v+(layer+1)*n 权值 0 v+layer*n → u+(layer+1)*n 权值 0
    跨层边权是 0,代表"这条边被免单";因为原图无向,免费边也要双向连。

第三步:跑一次 Dijkstra,终点取最小值

  • 起点:第 0 层的 1 号点,即节点 0(0-based 的 1 号)。
  • 终点候选:第 layer 层的 n 号点,即 n-1 + layer*n,对 layer = 0..k 取 dist 最小。
  • 边权非负,直接堆优化 Dijkstra,时间复杂度 O((k·n + k·m) log(k·n))。

等价写法:distuc 状态数组(更省事)

很多同学觉得"复制 k+1 层"占空间,其实本质就是二维状态 dist[u][c] = 到达站点 u、已用 c 张券的最短路。松弛时:

  • 普通走:dist[v][c] = min(dist[v][c], dist[u][c] + w)

  • 免费走:dist[v][c+1] = min(dist[v][c+1], dist[u][c] + 0)(需 c < k)

下面两份代码都采用"节点展开"的写法,最直观、最不容易写错。

C++ 参考代码

cpp 复制代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<ll, int> pli;
const ll INF = 1e18;

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    int n, m, k;
    cin >> n >> m >> k;

    // 新图:共 (k+1) 层,每层 n 个点
    vector<vector<pair<int, ll>>> g((k + 1) * n);

    for (int i = 0; i < m; i++) {
        int u, v; ll w;
        cin >> u >> v >> w;
        u--; v--;                       // 转成 0-based
        // 1) 层内边:每一层都照原图连
        for (int layer = 0; layer <= k; layer++) {
            int a = u + layer * n, b = v + layer * n;
            g[a].push_back({b, w});
            g[b].push_back({a, w});
        }
        // 2) 跨层边:用掉一张券,把这条边免单(双向)
        for (int layer = 0; layer < k; layer++) {
            g[u + layer * n].push_back({v + (layer + 1) * n, 0});
            g[v + layer * n].push_back({u + (layer + 1) * n, 0});
        }
    }

    // 3) Dijkstra,起点 = 第 0 层的 1 号点
    vector<ll> dist((k + 1) * n, INF);
    priority_queue<pli, vector<pli>, greater<pli>> pq;
    dist[0] = 0;
    pq.push({0, 0});

    while (!pq.empty()) {
        auto [d, u] = pq.top(); pq.pop();
        if (d > dist[u]) continue;       // 过期堆元素
        for (auto [v, w] : g[u]) {
            if (dist[v] > d + w) {
                dist[v] = d + w;
                pq.push({dist[v], v});
            }
        }
    }

    // 4) 终点:取所有层 n 号点的最小值
    ll ans = INF;
    for (int layer = 0; layer <= k; layer++)
        ans = min(ans, dist[n - 1 + layer * n]);

    cout << (ans == INF ? -1 : ans) << "\n";
    return 0;
}

Python 参考代码

python 复制代码
import heapq

def solve():
    n, m, k = map(int, input().split())
    g = [[] for _ in range((k + 1) * n)]

    for _ in range(m):
        u, v, w = input().split()
        u, v, w = int(u) - 1, int(v) - 1, int(w)
        # 1) 层内边
        for layer in range(k + 1):
            a = u + layer * n
            b = v + layer * n
            g[a].append((b, w))
            g[b].append((a, w))
        # 2) 跨层边(免费,双向)
        for layer in range(k):
            g[u + layer * n].append((v + (layer + 1) * n, 0))
            g[v + layer * n].append((u + (layer + 1) * n, 0))

    # 3) Dijkstra
    INF = 10 ** 18
    dist = [INF] * ((k + 1) * n)
    dist[0] = 0
    pq = [(0, 0)]
    while pq:
        d, u = heapq.heappop(pq)
        if d > dist[u]:
            continue
        for v, w in g[u]:
            if dist[v] > d + w:
                dist[v] = d + w
                heapq.heappush(pq, (dist[v], v))

    # 4) 终点取各层最小值
    ans = min(dist[n - 1 + layer * n] for layer in range(k + 1))
    print(-1 if ans == INF else ans)

if __name__ == "__main__":
    solve()

时间与空间复杂度

  • 时间复杂度 :新图点数 O(k·n),边数 O(k·m)(层内)+ O(k·m)(跨层)= O(k·m)。Dijkstra 用优先队列,整体 O((k·n + k·m) log(k·n))。
  • 空间复杂度 :邻接表 O(k·m),距离数组 O(k·n)。
  • 当 k 较小(如 ≤ 20)、n, m 在 10^5 级别时,本写法完全够用。若 k 很大(比如 k = n),需考虑 dist[u][c] 状态 DP + 更精细的剪枝,或改用其他建模。

易错点(七个)

  1. 层数开错 :必须建 k+1 层(0 到 k),只建 k 层会丢失"恰好用满 k 张券"的状态。
  2. 跨层边漏反向 :原图是无向边,免费跨层边也要双向连(从 u 到 v 的下一层、从 v 到 u 的下一层),只连单向会 WA。
  3. 跨层边权填错 :免费 = 权值 0,不是原权 w,更不是 -w 或 w 取反。
  4. 终点只取一层 :要用满 k 张券才最优?不一定!必须取所有 k+1 层中 n 号点的 dist 最小值。
  5. 下标与越界 :节点编号是 u + layer*n,数组大小要开 (k+1)*n;u 务必先转成 0-based,别混着 1-based 用。
  6. 溢出与不可达 :代价累加用 long long(C++)/ 大整数(Python);终点若全是 INF 要输出 -1,不能默认输出 0。
  7. 券是全局共享的 k 次:不是"每条边都能免费一次"!券数量固定为 k,跨层走一次消耗一张,这是分层图的核心设定,理解错模型就全错。

进阶

  • 瞬移任意两点("坐飞机"模型) :若券的效果是"瞬间移动到任意站点",那么跨层时不是对某条边免单,而是在第 i 层任意两点间连权 0 的边(注意这样边数会爆炸,通常需要结合"最短路预处理 + 仅从当前点连向全局最优点"等技巧)。
  • 反转边方向 k 次:把"免费"换成"把一条有向边反向且不花代价",跨层边改成翻转方向即可,建模完全同构。
  • 与 0-1 BFS 的关系 :当边权只有 0 和 1 时,可用双端队列的 0-1 BFS 替代 Dijkstra,降到 O(V+E);分层图是更通用的框架。
  • 与"次短路"的区别:次短路求的是"第二短",分层图求的是"带 k 次特殊操作的最短",两者建模不同,别混。
  • 状态压缩优化 :当 k 很大但"最优解几乎不会用满"时,可只保留 dist[u] 与其"用券数最少的那个状态"(即每个点只保留当前最优的用券数),能省不少空间。

小结与互动

分层图最短路的本质,就是给普通最短路加一个"操作次数"状态维 ,把"能不能免单"变成图上的一层一层。记住三句话:层内照常走、跨层权为 0、终点取各层最小,这类题基本就稳了。

这道题你在蓝桥杯/信息素养大赛里遇到过哪些"免费通行"变形?欢迎在评论区留言你见过的原题,咱们一起拆解建模思路。觉得有用就点个赞 + 收藏,关注我,每天一道信奥真题拆解。


📚 免费少儿编程资料(夸克网盘领取)

以下资料来自夸克网盘分享,点击链接可直接保存;若需在 App 内打开,也可复制下方明文链接:

  1. 全国青少年信息素养大赛复赛集训题目Python&C++.docx
    https://pan.quark.cn/s/93995d3cb150
  2. 2024信息素养-智能算法应用挑战赛-复赛初中组题目7月7日.pdf
    https://pan.quark.cn/s/da97b5dbf75d
  3. Python背记手册.pdf
    https://pan.quark.cn/s/7568ae9ca92b
  4. Python课程
    https://pan.quark.cn/s/a94bf02d00c6
  5. 2024信息素养大赛图形化复赛集训题答案3-9
    https://pan.quark.cn/s/6ccab7ec3cbc
  6. 2025年03月份电子学会考级真题
    https://pan.quark.cn/s/4403c4228912
  7. 2025全国青少年信息素养大赛赛项说明
    https://pan.quark.cn/s/d9d0df4a9f29
  8. 青少儿信息素养大赛编程资料
    https://pan.quark.cn/s/4ab6bd83be8a

资料持续更新,关注获取最新分享。

相关推荐
全栈练习生1 小时前
大模型原理之自适应推理
python·ai
YSL0701241 小时前
一文通关-----排序
数据结构·算法
实名上网宋凯宣9 小时前
Django
后端·python·django
如若12311 小时前
【论文复现】CoFailMed:从零复现医学图像分割的共享失败审计,含完整代码流程与科研绘图
人工智能·python
FlightYe11 小时前
视界原理之3D视频(四):裸眼、眼镜和头显
android·linux·c++·3d·ffmpeg·音视频
ZhengEnCi11 小时前
GPT-6 Astra 爆改 Qwen 权重真有那么神吗:权重空间编程 0.016% 参数的真相
算法·架构
罗西的思考11 小时前
【轻量化复刻大模型 / KIMI K3】KDA(Kimi Delta Attention):并行
人工智能·算法·机器学习
by2099912 小时前
C++ 智能指针的所有权与使用
开发语言·c++·经验分享
砚底藏山河12 小时前
python量化自定义周期 K 线合成
java·开发语言·数据库·python·金融