【题目描述】
给定一个 n 个点 m 条边的有向图,图中可能存在重边和自环, 边权可能为负数。
请你求出从 1 号点到 n 号点的最多经过 k 条边的最短距离,如果无法从 1 号点走到 n 号点,输出 impossible
。
注意:图中可能 存在负权回路 。
【输入格式】
第一行包含三个整数 n,m,k。
接下来 m 行,每行包含三个整数 x,y,z,表示存在一条从点 x 到点 y 的有向边,边长为 z。
【输出格式】
输出一个整数,表示从 1 号点到 n 号点的最多经过 k 条边的最短距离。
如果不存在满足条件的路径,则输出 impossible
。
【数据范围】
1≤n,k≤500,
1≤m≤10000,
任意边长的绝对值不超过 10000。
【输入样例】
3 3 1
1 2 1
2 3 1
1 3 3
【输出样例】
3
由于含有负权边,一定不可以使用Dijkstra。
Bellman-Ford算法的实现方式是每一次都对图中的所有边进行一次松弛操作,对于边(u,v):dist[v] = min(dist[v],dist[u] + w),每一轮都对边进行操作,当某一轮没有边再发生松弛操作即可停止。
在最短路存在的前提下,那么我们每一次的松弛操作都应当让最短路的边数+1,而最短路最多只有n-1条边,故最多循环n-1次,即可得出结果,而每次对边进行松弛时间复杂度是O(m),故总时间复杂度是O(nm)。
Bellman-Ford算法还可以检测图中是否存在负权环,当循环松弛操作n-1次后,第n次操作仍然有边发生了松弛操作,那么就意味着n个点的最短路可以拥有n条边,因此必然存在环,但一般判断负环不使用Bellman-Ford算法,而是用优化后的版本,SPFA算法,包括求最短路也是,那么Bellman-Ford算法还有什么优势呢?优势就在于本题,有边数限制的最短路问题只能用Bellman-Ford算法来求解。
需要注意的点:
①由于是每一次对边进行松弛操作,因此存储图的方式可以多变,只要能够每次都遍历完所有边即可,故用结构体存下所有的边会比邻接表书写起来更方便;
②每次松弛的时候,是使用上次松弛完的结果来计算本次的结果,因此计算的时候需要备份一次上次的结果,以免发生“串联更新”的问题,也就是使用本次松弛计算的结果来更新后续的结果;
③输出答案时,可能存在负权边更新了两个无法到达的点的情况,所以判断不能直接判断是否等于0x3f,比如1无法到达点5,也无法到达点7,但5->7的边权是-2,那么在每次松弛的时候,是会导致这个值变小一点点的。
1 #include <iostream> 2 #include <string.h> 3 using namespace std; 4 5 const int N = 501,M = 10009; 6 int n,m,k; 7 int dist[N],backup[N]; 8 struct Edge 9 { 10 int a,b,w; 11 }edges[M]; 12 13 void BellmanFord() 14 { 15 memset(dist,0x3f,sizeof dist); 16 dist[1] = 0; 17 for(int i = 0;i < k;++i)//限制k条边则松弛k次 18 { 19 memcpy(backup,dist,sizeof dist); 20 for(int j = 0;j < m;++j) 21 { 22 int a = edges[j].a; 23 int b = edges[j].b; 24 int w = edges[j].w; 25 dist[b] = min(dist[b],backup[a] + w); 26 } 27 } 28 if(dist[n] < 0x3f3f3f3f/2) 29 cout << dist[n] << endl; 30 else 31 cout << "impossible" << endl; 32 } 33 34 int main() 35 { 36 cin >> n >> m >> k; 37 for(int i = 0;i < m;++i) 38 { 39 int a,b,w; 40 cin >> a >> b >> w; 41 edges[i] = {a,b,w}; 42 } 43 44 BellmanFord(); 45 46 return 0; 47 }