题目链接:登录—专业IT笔试面试备考平台_牛客网
输入
4 4 1
1 2 10
2 4 10
1 3 1
3 4 100
输出
1
这个题在经典的找最短路径图论题上增加了一个可以修改k条路径长度(以下均用路径代替时间)为0这么一个条件。首先用邻接表来存储这个图,再通过dijkstra算法来更新最短路径。dijkstra算法代码的主要内容是维护两个集合,分别为已确定最短路径的结点集合A、这些结点向外扩散的邻居点集合B。在本题中选择的是维护最短路径的二维数组dis和邻居点优先队列hq。其中根据输出描述我们可以得到输出的最短路径必须是已改造道路数量不超过可改造道路数量的前提下进行,因此设立二维数组dis[i][j]表示从节点1到节点i在改造j条路情况下的最短路径,最后输出时应取dis[n]中的最小值。在dijkstra算法中每次更新是根据最短路径来找邻居,因此通过优先队列实现是比较方便的。在队列hq中每组元素为 (i,j,v) 三个元素,其中i = 当前最短路径 j = 当前顶点 v = 当前改造数量。
第一段代码:导入堆heapq模块,堆常常被当做优先队列使用,因为可以快速地访问到最小或者最大的元素。其中heapq堆数据结构最重要的特征是heap[0]永远是最小的元素。本题所给的图为双向图,因此在构建邻接表时两个方向都应该添加。给两个需要维护的数组定初值。
import heapq
n,m,k = map(int,input().split())
edge = [[] for _ in range(n + 1)] #双向图
dis = [[float('inf')] * (k + 1) for _ in range(n + 1)] #dis[i][j]表示从1到i改造j条路的最短时间
dis[1][0] = 0
hq = [(0,1,0)]
for _ in range(m):
p1,p2,t = map(int,input().split())
edge[p1].append((p2,t)) #p1点到p2点的时间
edge[p2].append((p1,t)) #p2点到p1点的时间
第二段代码:dijkstra算法的核心代码。用于更新dis。遍历hq中的所有节点,直到hq为空结束更新,就可以得到从起点1到其他所有节点的最短距离。在这段代码中,主要考虑两个情况:第一种情况,不改造道路,因为可以改造的道路数量有限,有些道路无法进行改造,要考虑的就是当前最短路径加上到下一个节点的路径是否小于dis中的路径,如果小于,则更新最短路径并将下一个节点放进hq中用于之后的更新;第二种情况,改造道路,前提是已改造道路数量小于可改造道路数量以及当前最短路径下上到下一个节点的路径(改造后为0)是否小于dis中的路径,如果小于,则更新最短路径并将下一个节点放进hq中用于之后的更新并将已改造道路数量+1。最后在dis[n]中取最小值并输出即可。
while hq:
i,j,v = heapq.heappop(hq) #i = 当前最短时间 j = 当前顶点 v = 当前改造数量
if i > dis[j][v]:
continue
for i1,j1 in edge[j]: #遍历j点可以到达的点
#不改造道路
if i + j1 < dis[i1][v]:
dis[i1][v] = i + j1
heapq.heappush(hq,(dis[i1][v],i1,v))
#改造道路
if v < k and i < dis[i1][v + 1]: #如果当前最短时间+0小于下一个点的最短时间
dis[i1][v + 1] = i
heapq.heappush(hq,(dis[i1][v + 1],i1,v + 1))
print(min(dis[n]))
完整代码如下。
import heapq
n,m,k = map(int,input().split())
edge = [[] for _ in range(n + 1)] #双向图
dis = [[float('inf')] * (k + 1) for _ in range(n + 1)] #dis[i][j]表示从1到i改造j条路的最短时间
dis[1][0] = 0
hq = [(0,1,0)]
for _ in range(m):
p1,p2,t = map(int,input().split())
edge[p1].append((p2,t)) #p1点到p2点的时间
edge[p2].append((p1,t)) #p2点到p1点的时间
while hq:
i,j,v = heapq.heappop(hq) #i = 当前最短时间 j = 当前顶点 v = 当前改造数量
if i > dis[j][v]:
continue
for i1,j1 in edge[j]: #遍历j点可以到达的点
#不改造道路
if i + j1 < dis[i1][v]:
dis[i1][v] = i + j1
heapq.heappush(hq,(dis[i1][v],i1,v))
#改造道路
if v < k and i < dis[i1][v + 1]: #如果当前最短时间+0小于下一个点的最短时间
dis[i1][v + 1] = i
heapq.heappush(hq,(dis[i1][v + 1],i1,v + 1))
print(min(dis[n]))
这道题最初的难点就在于全英文。说实话刚开始看了很久还是没有看懂题目意思,因为有些单词并不认识,唉,英语的痛,最终还是靠有道翻译读懂了题意。在以前遇到图论题的时候还不知道该如何存储图,而且遇到比较多的就是求最短路径问题了,之后就专门学了一下图论中的存储和dijkstra算法。在存储中主要有两种方式,分别是邻接矩阵和邻接表。邻接表是最常用的了,可以用于重边,只存储存在的边,但复杂度和速度上会比矩阵高一些和慢一些。图论算法上有拓朴排序、Floyd算法等等其实还没有学,也不是没有学,是初看的时候有点难理解,dijkstra算法算是在理解BFS基础上比较好理解的。很像贪心算法,抄最近的路走,再用优先队列和BFS很好的实现了搜索最短路径。这题想了很久主要卡在这个可修改路径该怎么去表示,一开始没想到用二维数组存从节点1到节点i在改造j条路情况下的最短路径,想的是直接存最短路径后再去修改j条路径,到后面发现是有问题的,无法保证在最短路径的情况下减掉k个最长路径就是最短的。之后可能是因为最近动态规划的题目印象比较深,这题本质上也是更新最短路径和动态规划也挺相似的,我就想着能不能用多维数组来表示。一开始我还用的是三维数组存起点、终点和改造数量,后来发现没必要,反正都是从起点开始的,所以就改成了二维数组,再通过BFS不断更新,其实就是一个搜索的过程而已。还有关于定初始值上下限的问题,我的总结是以后都用float('inf')来比较好,就不用考虑上下限的问题了,第一次提交答案错误就是因为上限不够高的问题,之前遇到过好几次这个问题,所以这次也是很快的发现之后并改掉了,否则又要看半天还以为是下面算法的错。