目录
受限路径计数问题详解 —— 基于节点到终点最短距离的动态规划
受限路径计数问题详解 —— 基于节点到终点最短距离的动态规划
题目描述
给定一个加权无向连通图,图中有 n 个节点,编号从 1 到 n。
图中包含一组边 edges,每条边 edges[i] = [u_i, v_i, w_i] 表示节点 u_i 和 v_i 之间存在一条权重为 w_i 的无向边。
定义:
- 路径:节点序列
[z_0, z_1, ..., z_k],满足z_0 = start,z_k = end,且对于所有0 <= i < k,(z_i, z_{i+1})之间存在边。 - 路径距离:路径上所有边权重之和。
- 记
distanceToLastNode(x)为节点x到节点n的最短路径距离。
受限路径定义为一条路径 [z_0, z_1, ..., z_k],其中 z_0 = 1,z_k = n,且满足对于所有 0 <= i < k,都有:
distanceToLastNode(z_i) > distanceToLastNode(z_{i+1})
即路径上的节点到终点的距离严格递减。
求从节点 1 到节点 n 的所有受限路径的数量,并对结果取模 10^9 + 7。
解题分析
题意理解
题目要求计算满足“距离递减条件”的所有路径数量。这个限制使得路径不能随意走,需要路径上的节点按照 distanceToLastNode 递减,从而避免出现环路。
关键点
- 求最短距离:计算所有节点到终点
n的最短距离distanceToLastNode。 - 路径计数:统计从 1 到 n 的所有符合距离递减条件的路径数量。
- 路径严格递减:节点之间必须满足
distanceToLastNode(当前节点) > distanceToLastNode(下一个节点)。
解题方法
1. 计算最短距离(Dijkstra算法)
为了方便判定“距离递减”关系,我们从终点节点 n 反向使用 Dijkstra 算法计算到所有节点的最短距离 distanceToLastNode。
- 为什么从
n反向?
这样计算出每个节点到n的最短距离,方便我们在后续路径计数中直接判断邻居节点的距离大小关系。
2. 动态规划计数受限路径
定义 dp[x] 表示从节点 x 到节点 n 的受限路径数。
- 边界条件:
dp[n] = 1,因为从终点到终点只有一条路径。 - 状态转移:
对于每个节点x,
dp[x] = Σ dp[y],其中y是x的邻居且满足distanceToLastNode[x] > distanceToLastNode[y]。
这样,只有满足距离递减条件的路径才被计数。
3. 计算顺序
由于 dp[x] 依赖于邻居节点 y 且 distanceToLastNode[y] < distanceToLastNode[x],所以节点必须按 distanceToLastNode 从小到大顺序遍历,这样能保证计算某个节点时,其可转移的邻居的 dp 值已计算完成。
代码实现(Python)
import heapq
class Solution:
def countRestrictedPaths(self, n: int, edges: list[list[int]]) -> int:
MOD = 10**9 + 7
graph = [[] for _ in range(n + 1)]
# 构建图的邻接表
for u, v, w in edges:
graph[u].append((v, w))
graph[v].append((u, w))
# 1. Dijkstra算法:从节点 n 反向计算最短距离
dist = [float('inf')] * (n + 1)
dist[n] = 0
heap = [(0, n)]
while heap:
cur_dist, u = heapq.heappop(heap)
if cur_dist > dist[u]:
continue
for v, w in graph[u]:
if dist[v] > dist[u] + w:
dist[v] = dist[u] + w
heapq.heappush(heap, (dist[v], v))
# 2. 按最短距离从小到大排序节点
nodes = list(range(1, n + 1))
nodes.sort(key=lambda x: dist[x])
# 3. 动态规划计算受限路径数
dp = [0] * (n + 1)
dp[n] = 1 # 终点的受限路径数为1
for u in nodes:
for v, _ in graph[u]:
if dist[u] > dist[v]:
dp[u] = (dp[u] + dp[v]) % MOD
return dp[1]
复杂度分析
- 时间复杂度
-
- Dijkstra 算法:
O(E log V),其中E是边数,V是节点数。 - 动态规划遍历每个节点和边:
O(E)。
总体时间复杂度为O(E log V)。
- Dijkstra 算法:
- 空间复杂度
-
- 存储图邻接表和距离数组:
O(V + E)。
- 存储图邻接表和距离数组:
示例说明
考虑如下示例:
n = 5
edges = [
[1, 2, 3],
[1, 3, 3],
[2, 3, 1],
[1, 4, 2],
[5, 2, 2],
[3, 5, 1],
[5, 4, 10]
]
步骤分析:
- 先从节点 5 计算最短距离到各点:
-
- distanceToLastNode(5) = 0
- distanceToLastNode(3) = 1 (5→3)
- distanceToLastNode(2) = 2 (5→2)
- distanceToLastNode(1) = 4 (1→3→5)
- distanceToLastNode(4) = 6 (4→1→3→5)
- 节点按距离排序:5 (0), 3 (1), 2 (2), 1 (4), 4 (6)
- 动态规划计算路径数:
-
- dp[5] = 1
- dp[3] = sum of dp[neighbors with smaller distance]: neighbors are 1(4),2(2),5(0)
-
-
- dist[3] = 1, 所以比 1 小的邻居只有 5,dp[3] = dp[5] = 1
-
-
- dp[2] = neighbors 1(4),3(1),5(0)
-
-
- dist[2] = 2,邻居中比 2 小的有 3(1)和5(0)
- dp[2] = dp[3] + dp[5] = 1 + 1 = 2
-
-
- dp[1] = neighbors 2(2),3(1),4(6)
-
-
- dist[1] = 4,邻居中比 4 小的是 2(2)和3(1)
- dp[1] = dp[2] + dp[3] = 2 + 1 = 3
-
-
- dp[4] = neighbors 1(4),5(0)
-
-
- dist[4] = 6,邻居中比 6 小的是 1(4)和5(0)
- dp[4] = dp[1] + dp[5] = 3 + 1 = 4
-
最终返回 dp[1] = 3,即从节点 1 到节点 5 的受限路径共有 3 条。
总结
这道题利用了Dijkstra算法与动态规划的结合:
- 先通过 Dijkstra 算法计算每个节点到终点的最短距离,为路径的严格递减条件提供依据。
- 然后基于最短距离的大小关系,设计状态转移动态规划,统计所有符合条件的路径数量。
关键思路是“从终点反向计算最短距离”,以及“按距离排序状态转移”,保证递推无环且正确。
这类题目非常经典,适合锻炼图的最短路算法和基于条件的路径计数技巧。

被折叠的 条评论
为什么被折叠?



