题解: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
【核心思想】
-
问题分析:给定有向图 \(G=(V,E)\),其中 \(|V|=n\),\(|E|=m\),边权可能为负数,可能存在负权回路。需要求从起点 \(1\) 到终点 \(n\) 的最多经过 \(k\) 条边的最短路径长度。这是一个带边数限制的单源最短路径问题,由于边权可能为负,不能使用 Dijkstra 算法,需要使用 Bellman-Ford 算法或其优化版本。
-
算法选择:
- Bellman-Ford 算法(边数限制版):通过限制松弛操作的轮数为 \(k\),确保路径最多包含 \(k\) 条边
- 距离数组 \(dist[]\):\(dist[i]\) 表示从起点到节点 \(i\) 的当前最短距离估计值
- 备份数组 \(backup[]\):防止同轮迭代中的串联更新,确保每轮只增加一条边
- 边集数组 \(edges[]\):存储所有边的信息 \((a, b, w)\),其中 \(a\) 为起点,\(b\) 为终点,\(w\) 为权重
-
关键步骤:
- 初始化:
- 读取 \(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]\) 作为答案
- 若 \(dist[n] > INF/2\),说明不存在满足条件的路径,输出
- 初始化:
-
时间/空间复杂度:
- 时间复杂度:\(O(k \cdot m)\),共进行 \(k\) 轮松弛,每轮遍历所有 \(m\) 条边
- 空间复杂度:\(O(n + m)\),距离数组 \(O(n)\),备份数组 \(O(n)\),边集数组 \(O(m)\)
- 适用于边数限制的最短路问题,可以处理负权边,能检测负权回路(当 \(k \geq n\) 时)
-
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
浙公网安备 33010602011771号