Luogu P1807 最长路
核心思路
本题求解的是有向无环图(DAG)中的最长路问题。这是一个经典的动态规划问题。
与求最短路不同(Dijkstra 等算法),最长路问题在普通图中是 NP-hard 问题,但在有向无环图中,可以利用其拓扑性高效求解。
状态定义
我们定义 \(dp_i\) 为:从起点 \(1\) 到达顶点 \(i\) 的最长路径长度。
状态转移
为了计算 \(dp_i\),我们需要找到所有能到达 \(i\) 的前驱顶点 \(u\)。对于每一条边 \((u, v)\),其权值为 \(w\),我们可以用 \(1 \to u\) 的最长路径加上边 \((u, v)\) 的长度来尝试更新 \(1 \to v\) 的最长路径。
状态转移方程为:$ dp_v = \max(dp_v, dp_u + w) $。
关键点:拓扑序
要正确进行状态转移,当我们计算 \(dp_v\) 时,必须保证所有前驱节点 \(u\) 的 \(dp_u\) 值都已经计算完毕。这要求我们按照图的拓扑序列来进行递推。
题目中有一个非常重要的隐藏条件:对于每一条边 \((u, v, w)\),都有 \(u < v\)。这意味着顶点的编号序列 \(1, 2, \dots, n\) 本身就是一个合法的拓扑序!因此,我们无需再进行拓扑排序,直接按顶点编号从小到大进行循环更新即可。
算法步骤
初始化
创建一个 \(dp\) 数组,将 \(dp_1\) 初始化为 \(0\)(起点到自身的距离为0),其他所有 \(dp_i\) 初始化为负无穷,表示尚未到达。
递推
按照顶点编号 \(u\) 从 \(1\) 到 \(n\) 的顺序进行遍历。
- 如果 \(dp_u\) 为负无穷,说明点 \(u\) 从起点不可达,跳过。
- 否则,遍历所有从 \(u\) 出发的边 \((u, v, w)\),使用 \(dp_u + w\) 来更新 \(dp_v\) 的值,即
dp[v] = max(dp[v], dp[u] + w)。
输出结果
循环结束后,\(dp_n\) 的值就是从 \(1\) 到 \(n\) 的最长路径。如果 \(dp_n\) 仍然是负无穷,说明从 \(1\) 无法到达 \(n\),输出 \(-1\)。
代码解读
import sys
# 使用快读
input = sys.stdin.readline
# 读入点数 n 和边数 m
n, m = map(int, input().split())
# 邻接表存图
graph = [[] for _ in range(n + 1)]
for _ in range(m):
u, v, w = map(int, input().split())
graph[u].append((v, w))
# dp 数组,dp[i] 表示从 1 到 i 的最长路
# 初始化为负无穷,表示不可达
dp = [-float('inf')] * (n + 1)
# base case: 起点 1 到自身的距离为 0
dp[1] = 0
# 按照拓扑序(1, 2, ..., n)进行动态规划
for u in range(1, n + 1):
# 如果当前点 u 不可达,则无法从它出发更新其他点,跳过
if dp[u] == -float('inf'):
continue
# 遍历 u 的所有出边 (u, v, w)
for v, w in graph[u]:
# 用 u 的最长路来更新 v 的最长路(状态转移)
dp[v] = max(dp[v], dp[u] + w)
# 检查终点 n 是否可达
if dp[n] == -float('inf'): print(-1)
else: print(int(dp[n]))

浙公网安备 33010602011771号