带节点reduction值约束的加权无向图最短路径求解问题
这是个很有意思的最短路径变种问题,核心难点在于如何合理利用途经节点的reduction值来削减某条边的成本,同时保证路径总长度最短。咱们可以通过扩展经典Dijkstra算法的状态空间来解决,思路如下:
核心思路:跟踪可用的最大reduction值
普通Dijkstra只跟踪「到达某节点的最短距离」,但这里我们需要额外记录「当前是否持有未使用的reduction值,以及这个值的大小」。不过有个关键优化:对于同一个节点,如果你持有一个更大的reduction值且到达路径更短,那么持有更小reduction值的状态完全可以忽略——毕竟用大的reduction削减边权肯定更划算,后续路径的灵活性也更高。
所以我们定义两种核心状态:
(u, 0):到达节点u时,没有可用的reduction值,记录此时的最短路径长度(u, r):到达节点u时,持有一个最大为r的未使用reduction值,记录此时的最短路径长度
状态转移规则
假设当前状态是(u, current_r),路径长度为d,节点u的reduction值为ru,邻边u-v的权值为w:
收集当前节点的reduction:
如果ru > current_r,我们可以更新状态为(u, ru),路径长度仍为d(只是收集reduction,没有移动)。如果这个状态的路径长度比之前记录的更短,就加入优先队列。遍历邻边的两种选择:
- 不使用reduction:直接走边u-v,新路径长度为
d + w,新状态为(v, current_r)。如果这个长度比(v, current_r)的已有最短距离小,就更新并加入队列。 - 使用reduction(仅当current_r > 0):计算削减后的边权为
max(0, w - current_r),新路径长度为d + max(0, w - current_r),新状态为(v, 0)(因为reduction已经用完)。同样,若该长度更优则更新队列。
- 不使用reduction:直接走边u-v,新路径长度为
算法步骤
初始化:
- 起点的初始状态为
(start, 0),路径长度为0。 - 同时,若起点的reduction值
r_start > 0,添加状态(start, r_start),路径长度也为0(收集起点的reduction)。 - 用一个优先队列(最小堆)来存储待处理的状态,队列元素格式为
(当前路径长度, 当前节点, 可用reduction值)。 - 用两个数组(或字典)分别记录每个节点的两种状态的最短距离:
dist_no_reduce(对应(u,0))和dist_with_reduce(对应(u,r),同时记录当前最大的r值)。
- 起点的初始状态为
执行Dijkstra算法:
- 每次从队列中取出路径长度最短的状态。
- 如果当前状态的路径长度已经大于记录的最短距离,直接跳过(因为已经有更优的路径到达该状态)。
- 按照上述状态转移规则处理当前节点的reduction和所有邻边,更新对应的状态距离并加入队列。
提取结果:
终点的最短路径长度是min(dist_no_reduce[end], dist_with_reduce[end])——因为到达终点时,是否持有未使用的reduction不影响路径完成,取两种状态中的最小值即可。
示例说明
假设我们有图:A -5-> B -10-> C,其中A的reduction为3,B的reduction为8,求A到C的最短路径:
- 初始状态:
(A,0)(距离0)、(A,3)(距离0) - 从
(A,0)走到B,得到(B,0)(距离5),收集B的reduction后得到(B,8)(距离5) - 从
(A,3)走到B,得到(B,3)(距离5),但(B,8)的距离相同且reduction更大,所以这个状态可以忽略 - 从
(B,0)走到C,距离为5+10=15,状态(C,0) - 从
(B,8)走到C,使用reduction削减边权为max(0,10-8)=2,总距离为5+2=7,状态(C,0) - 最终C的最短路径长度为7,符合预期
实现注意事项
- 优先队列要按照路径长度从小到大排序,保证每次处理的都是当前最优状态。
- 对于
dist_with_reduce,如果新状态的reduction值更大且路径长度更短或相等,才需要更新——否则旧状态更优,无需处理。 - 边权削减后不能小于0,必须用
max(0, w - r)计算实际成本。
内容的提问来源于stack exchange,提问作者Faithe

