AcWing 853. 有边数限制的最短路

AcWing 853. 有边数限制的最短路

一、题目描述

给定一个 n 个点 m 条边的有向图,图中可能存在重边和自环, 边权可能为负数。

请你求出从 1 号点到 n 号点的最多经过 k 条边的最短距离,如果无法从 1 号点走到 n 号点,输出 impossible

注意:图中可能 存在负权回路 。

输入格式
第一行包含三个整数 n,m,k

接下来 $$ 行,每行包含三个整数 x,y,z,表示存在一条从点 x 到点 y 的有向边,边长为 z

点的编号为 1n

输出格式
输出一个整数,表示从 1 号点到 n 号点的最多经过 k 条边的最短距离。

如果不存在满足条件的路径,则输出 impossible

数据范围
1n,k500,1m10000,1x,yn
任意边长的绝对值不超过 10000

输入样例:

3 3 1
1 2 1
2 3 1
1 3 3

输出样例:

3
3 3 1
1 2 1
2 3 1
1 3 3

输出样例:

3

图解算法见这里

陈小玉老师的视频

二、BellmanFord算法

BellmanFord算法的实现方式是每一次都对图中的所有边进行一次松弛操作,对于边(u,v):dist[v] = min(dist[v],dist[u] + w),每一轮都对边进行操作,当某一轮没有边再发生松弛操作即可停止。

在最短路存在的前提下,那么我们每一次的松弛操作都应当让最短路的边数+1,而最短路最多只有n1条边,故最多循环n1次,即可得出结果,而每次对边进行松弛时间复杂度是O(m),故总时间复杂度是O(nm)

BellmanFord算法还可以检测图中是否存在负权环,当循环松弛操作n1次后,第n次操作仍然有边发生了松弛操作,那么就意味着n个点的最短路可以拥有n条边,因此必然存在环,但一般判断负环不使用BellmanFord算法,而是用优化后的版本SPFA算法,包括求最短路也是,那么BellmanFord算法还有什么优势呢?优势就在于本题,有边数限制的最短路问题只能用BellmanFord算法来求解。

需要注意的点:

① 每次松弛的时候,是使用上次松弛完的结果来计算本次的结果,因此计算的时候需要备份一次上次的结果,以免发生“串联更新”的问题,也就是使用本次松弛计算的结果来更新后续的结果;

② 输出答案时,可能存在负权边更新了两个无法到达的点的情况,所以判断不能直接判断是否等于0x3f,比如1无法到达点5,也无法到达点7,但5->7的边权是-2,那么在每次松弛的时候,是会导致这个值变小一点点的。

要点总结

  • bellmanford可以处理负权边,而Dijkstra只能处理正权边。

  • bellmanford算法可以处理负权回路,因为它求得的最短路是有限制的,是限制了边数的,这样不会永久的走下去,会得到一个解;

  • SPFA算法各方面优于该算法,但是在碰到 限制了最短路径长度 时就只能用bellmanford了,此时直接把n重循环改成k次循环即可

三、实现代码

#include <bits/stdc++.h>
using namespace std;
const int N = 1e4 + 10;
const int INF = 0x3f3f3f3f;
int n, m, T;
// 邻接表
int idx, e[N], ne[N], h[N], w[N];
void add(int a, int b, int c) {
    e[idx] = b, ne[idx] = h[a], w[idx] = c, h[a] = idx++;
}

int backup[N]; // 拷贝一下d数组,避免出现串联
int d[N];      // 最短距离

int bellman_ford() {
    memset(d, 0x3f, sizeof d);
    d[1] = 0;

    while (T--) {
        memcpy(backup, d, sizeof d);            // 每次更新前memcpy避免出现串联
        for (int u = 1; u <= n; u++)            // 执行n次松驰操作
            for (int i = h[u]; ~i; i = ne[i]) { // 枚举每个出边
                int v = e[i];
                d[v] = min(d[v], backup[u] + w[i]);
            }
    }
    if (d[n] > INF / 2) return INF; // 因为有负权边所以可能出现d[n]比0x3f3f3f3f稍微小一些
    return d[n];
}
int main() {
    cin >> n >> m >> T;
    memset(h, -1, sizeof h);
    while (m--) {
        int a, b, c;
        cin >> a >> b >> c;
        add(a, b, c);
    }
    int t = bellman_ford();
    if (t == INF)
        puts("impossible");
    else
        cout << t << endl;
    return 0;
}

四、答疑时间

Q:为什么使用了>INF/2这样的代码?

比如说这样:

这个奇葩起点和终点居然不连通
4号点在经过松弛操作后可能会更新5号点,因为正无穷2<正无穷嘛
所以终点就不是正无穷了,所以就返回正无穷2了,不对
又因为如果正无穷减也不会减的太大(数据保证边权的绝对值不大于100000

所以就直接这样写

if(d[n]>=0x3f3f3f3f/2)return INF;
return d[n];
posted @   糖豆爸爸  阅读(795)  评论(0编辑  收藏  举报
编辑推荐:
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
阅读排行:
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!
Live2D
点击右上角即可分享
微信分享提示