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])) 
posted @ 2025-08-09 15:45  AFewMoon  阅读(10)  评论(0)    收藏  举报