一、题目描述
给定一个 n n n 个点 m m m 条边的有向图,图中可能存在重边和自环, 边权可能为负数。
请你求出从
1
1
1 号点到
n
n
n 号点的最多经过
k
k
k 条边的最短距离,如果无法从
1
1
1 号点走到
n
n
n 号点,输出 impossible
。
注意:图中可能 存在负权回路 。
输入格式
第一行包含三个整数
n
,
m
,
k
n,m,k
n,m,k。
接下来 m m m 行,每行包含三个整数 x , y , z x,y,z x,y,z,表示存在一条从点 x x x 到点 y y y 的有向边,边长为 z z z。
输出格式
输出一个整数,表示从
1
1
1 号点到
n
n
n 号点的最多经过
k
k
k 条边的最短距离。
如果不存在满足条件的路径,则输出 impossible
。
数据范围
1
≤
n
,
k
≤
500
,
1≤n,k≤500,
1≤n,k≤500,
1
≤
m
≤
10000
,
1≤m≤10000,
1≤m≤10000,
任意边长的绝对值不超过
10000
10000
10000。
输入样例:
3 3 1
1 2 1
2 3 1
1 3 3
输出样例:
3
二、Bellman-Ford算法
Bellman-Ford算法可以处理 有负权边 的最短路问题、只经过 k k k 条边的最短路问题,但是求负权边的最短路一般只用spfa算法,因此除了 k k k 边最短路问题,我们一般都不使用 Bellman-Ford算法。该算法的时间复杂度为 O ( n m ) O(nm) O(nm)。
Bellman-Ford算法伪代码:
本质思想:尝试能否利用
源
点
→
a
→
b
源点 → a → b
源点→a→b 来减少
源
点
→
b
源点 → b
源点→b 的距离。
// 边的结构体定义
struct
{
int a, b, w;
} e[M];
// 进行k次迭代,表示最多经过k条边可以到达的最短路
for (int i = 0; i < n; i++)
{
// 每次循环所有边,格式为(a, b, w),即a → b,权重是w
// 因此这里所有边的遍历方式不一定使用邻接表,可以用傻瓜式的结构体数组存储
// dist[i] 表示从源点到i的距离
for (int j = 0; j < m; j++)
dist[b] = min(dist[b], dist[a] + w); // 松弛操作
}
完成上述循环之后能够满足对于所有的边都有 d i s t [ b ] ≤ d i s t [ a ] + w dist[b] ≤ dist[a] + w dist[b]≤dist[a]+w。
这里有一个问题需要声明:如果图中 存在负权回路,则最短路不一定存在。而 Beillman-Ford算法可以判断出图中是否存在负权回路。因为上述伪代码中的迭代次数是有意义的,比如说我们当前迭代了 k k k 次,此时获得的 d i s t dist dist 数组是 从源点出发,经过不超过 k k k 条边,走到每个点的最短距离。如果我们在进行第 n n n 次迭代的时候, d i s t dist dist 数组又发生了变化,则说明在这个最短路中,存在一条经过 n n n 条边的最短路,有 n n n 条边说明有 n + 1 n + 1 n+1 个结点,但是我们一共只有 n n n 个点,因此由于抽屉原理,这 n + 1 n + 1 n+1 个点中一定有两个结点完全一样,那么这个路径上一定存在负环。
因此,Bellman-Ford算法可以用来寻找负环,但是一般而言,寻找负环我们常常使用 spfa判断负环算法,我们后面会介绍。
但是有一类题目只能用Bellman-Ford算法来写,那就是经过最多 k k k 条边的最短路径问题,这种问题只能使用Bellman-Ford算法。
三、代码
代码中有不少细节,注意看注释。
#include <iostream>
#include <cstring>
using namespace std;
const int N = 510, M = 1e4 + 10;
struct Edge
{
int a, b, w;
} edge[M];
int dist[N], backup[N];
int n, m, k;
int bellman_ford()
{
memset(dist, 0x3f, sizeof dist);
dist[1] = 0;
// 题目要求不超过k条边
for (int i = 0; i < k; i++)
{
// 所有迭代过程中都是依赖上一次的dist
// 如果不做备份,当前dist值会被其他的边更新过了,导致错误
memcpy(backup, dist, sizeof dist);
for (int j = 0; j < m; j++)
{
int a = edge[j].a, b = edge[j].b, w = edge[j].w;
// 只用上一次迭代的结果更新当前的距离
dist[b] = min(dist[b], backup[a] + w);
}
}
// 为什么要这么判断呢?
// 因为可能存在: i号结点 → n号结点的边,边权为-2
// 但是i号结点本来就不可达,dist[i]是0x3f3f3f3f,dist[n]是0x3f3f3f3f
// 但是dist[n]可以被dist[i]更新为0x3f3f3f3f - 2,因此不可以用“==”符号
if (dist[n] > 0x3f3f3f3f / 2) return -1;
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);
edge[i] = {a, b, w};
}
int t = bellman_ford();
if (t == -1) puts("impossible");
else printf("%d\n", t);
return 0;
}