题解:AcWing 853 有边数限制的最短路

【题目来源】

AcWing:853 有边数限制的最短路 - AcWing题库

【题目描述】

给定一个\(n\)个点\(m\)条边的有向图,图中可能存在重边和自环,边权可能为负数。请你求出从\(1\)号点到\(n\)号点的最多经过\(k\)条边的最短距离,如果无法从\(1\)号号点走到\(n\)号点,输出impossible。注意:图中可能存在负权回路

【输入】

第一行包含三个整数 \(n,m,k\)。接下来\(m\)行,每行包含三个整数\(x,y,z\),表示存在一条从点\(x\) 到点\(y\)的有向边,边长为\(z\)。点的编号为\(1\sim n\)

【输出】

输出一个整数,表示从\(1\)号点到\(n\)号点的最多经过\(k\)条边的最短距离。如果不存在满足条件的路径,则输出 impossible。

【输入样例】

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

【输出样例】

3

【核心思想】

  1. 问题分析:给定有向图 \(G=(V,E)\),其中 \(|V|=n\)\(|E|=m\),边权可能为负数,可能存在负权回路。需要求从起点 \(1\) 到终点 \(n\)最多经过 \(k\) 条边的最短路径长度。这是一个带边数限制的单源最短路径问题,由于边权可能为负,不能使用 Dijkstra 算法,需要使用 Bellman-Ford 算法或其优化版本。

  2. 算法选择

    • Bellman-Ford 算法(边数限制版):通过限制松弛操作的轮数为 \(k\),确保路径最多包含 \(k\) 条边
    • 距离数组 \(dist[]\)\(dist[i]\) 表示从起点到节点 \(i\) 的当前最短距离估计值
    • 备份数组 \(backup[]\):防止同轮迭代中的串联更新,确保每轮只增加一条边
    • 边集数组 \(edges[]\):存储所有边的信息 \((a, b, w)\),其中 \(a\) 为起点,\(b\) 为终点,\(w\) 为权重
  3. 关键步骤

    • 初始化
      • 读取 \(n\)(节点数)、\(m\)(边数)、\(k\)(最多边数限制)
      • 读入 \(m\) 条边 \((x, y, z)\),存储到 \(edges\) 数组
      • 初始化距离数组 \(dist[i] = INF\)\(dist[1] = 0\)(起点距离为0)
    • 主循环(执行 \(k\) 轮松弛操作):
      • 备份距离数组memcpy(backup, dist, sizeof(dist)),防止同轮串联更新
      • 遍历所有边(共 \(m\) 条边):
        • 对于每条边 \((a, b, w)\)
        • \(backup[a] + w < dist[b]\),则更新 \(dist[b] = backup[a] + w\)
        • 使用 \(backup[a]\) 而非 \(dist[a]\),确保本轮每条边只被使用一次
    • 返回结果
      • \(dist[n] > INF/2\),说明不存在满足条件的路径,输出 impossible
      • 否则输出 \(dist[n]\) 作为答案
  4. 时间/空间复杂度

    • 时间复杂度:\(O(k \cdot m)\),共进行 \(k\) 轮松弛,每轮遍历所有 \(m\) 条边
    • 空间复杂度:\(O(n + m)\),距离数组 \(O(n)\),备份数组 \(O(n)\),边集数组 \(O(m)\)
    • 适用于边数限制的最短路问题,可以处理负权边,能检测负权回路(当 \(k \geq n\) 时)
  5. Bellman-Ford 算法的核心思想

    • 松弛操作:通过反复松弛边来逐步逼近最短路径,第 \(i\) 轮松弛后得到最多经过 \(i\) 条边的最短路径
    • 边数限制原理:限制松弛轮数为 \(k\),则得到的路径最多包含 \(k\) 条边
    • 备份数组作用:防止同轮迭代中的串联更新,确保每轮只增加一条边,避免使用超过 \(k\) 条边
    • 负权边处理:不同于 Dijkstra 的贪心策略,Bellman-Ford 通过多轮松弛可以正确处理负权边
    • 负权回路检测:若进行 \(n\) 轮松弛后仍能更新距离,说明存在负权回路
    • 适用于带负权边的单源最短路径问题、边数限制的最短路问题

【解题思路】

【解题思路2】

【算法标签】

Bellman-Ford

【代码详解】

#include <bits/stdc++.h>
using namespace std;

// 定义常量,N为最大节点数,M为最大边数,INF为无穷大
const int N = 510, M = 10010, INF = 1e9;

// 定义距离数组dist和备份数组backup
int dist[N], backup[N];

// 定义边结构体,包含起点a,终点b,权重w
struct edge {
    int a, b, w;
};

// 定义边数组edges
edge edges[M];

// 定义节点数n,边数m,最多迭代次数k
int n, m, k;

// Bellman-Ford算法实现
int bellman() {
    // 初始化距离数组dist为无穷大,起点距离设为0
    fill(dist, dist + N, INF);
    dist[1] = 0;

    // 进行k次迭代
    for (int i = 0; i < k; i++) {
        // 备份当前的距离数组
        memcpy(backup, dist, sizeof(dist));

        // 遍历所有边,更新距离数组
        for (int j = 0; j < m; j++) {
            int a = edges[j].a, b = edges[j].b, w = edges[j].w;
            dist[b] = min(dist[b], backup[a] + w);
        }
    }

    // 返回终点n的距离,如果距离仍为无穷大则返回-1
    return dist[n];
}

int main() {
    // 输入节点数n,边数m,最多迭代次数k
    cin >> n >> m >> k;

    // 输入每条边的起点a,终点b,权重w
    for (int i = 0; i < m; i++) {
        int a, b, w;
        cin >> a >> b >> w;
        edges[i] = {a, b, w};
    }

    // 调用Bellman-Ford算法计算最短路径
    int ans = bellman();

    // 如果结果大于等于INF/2,说明不存在从起点到终点的路径,输出"impossible"
    if (ans >= INF / 2) cout << "impossible";
    else cout << dist[n] << endl;

    return 0;
}

#include <bits/stdc++.h>
using namespace std;

const int N = 510;      // 最大节点数
const int M = 10010;    // 最大边数
const int INF = 0x3f3f3f3f;  // 无穷大,表示不可达

int n;      // 节点数量
int m;      // 边数量
int k;      // 最多经过的边数(最短路径的最大边数)
int dist[N];  // 当前轮次的最短距离数组
int backup[N];  // 上一轮次的最短距离数组,用于防止串联更新
struct Edge
{
    int a, b, w;  // 边的起点、终点、权重
} edges[M];  // 存储所有边的数组

// Bellman-Ford算法求单源最短路径
// 返回值:从节点1到节点n的最短距离,如果不存在这样的路径返回INF
int bellman_ford()
{
    // 第一步:初始化距离数组
    memset(dist, 0x3f, sizeof(dist));  // 所有距离初始化为无穷大
    dist[1] = 0;  // 起点到自身的距离为0

    // 第二步:进行k轮松弛操作
    // 每轮松弛:尝试用所有边更新最短距离
    for (int i = 0; i < k; i++)
    {
        // 备份上一轮的距离数组,防止本轮更新串联
        memcpy(backup, dist, sizeof(dist));

        // 遍历所有边,进行松弛操作
        for (int j = 0; j < m; j++)
        {
            int a = edges[j].a;  // 边的起点
            int b = edges[j].b;  // 边的终点
            int w = edges[j].w;  // 边的权重

            // 松弛操作:如果通过边a->b可以使得到b的距离更短,则更新
            // 使用backup[a]而不是dist[a],确保本轮更新不互相影响
            if (backup[a] != INF)  // 避免INF + w溢出
            {
                dist[b] = min(dist[b], backup[a] + w);
            }
        }
    }

    // 第三步:返回结果
    return dist[n];
}

int main()
{
    // 输入节点数、边数、最大边数限制
    scanf("%d%d%d", &n, &m, &k);

    // 输入所有边
    for (int i = 0; i < m; i++)
    {
        int a, b, w;
        scanf("%d%d%d", &a, &b, &w);
        edges[i] = {a, b, w};  // 存储边
    }

    // 执行Bellman-Ford算法
    int t = bellman_ford();

    // 输出结果
    // 注意:因为存在负权边,dist[n]可能比INF小,但不能小于INF/2
    if (t > INF / 2)
    {
        puts("impossible");  // 不存在从1到n的最短路径
    }
    else
    {
        printf("%d\n", t);
    }

    return 0;
}

【运行结果】

3 3 1
1 2 1
2 3 1
1 3 3
3
posted @ 2026-06-16 10:14  团爸讲算法  阅读(7)  评论(0)    收藏  举报