Luogu P1983 [NOIP 2013 普及组] 车站分级
核心思路
本题要求解最少需要划分的车站级别数。我们可以将问题转化为图论中的最长路问题。关键在于分析题目给出的约束条件:对于任意一趟车,如果它停靠了车站 \(x\),那么在它行驶区间内所有级别不低于 \(x\) 的车站,它都必须停靠。
反过来思考这个条件:对于一趟车,如果在其运行区间(始发站到终点站)内,有一个车站 \(j\) 没有停靠,而车站 \(k\) 停靠了,那么我们可以推断出车站 \(j\) 的级别一定低于车站 \(k\) 的级别。即 level(j) < level(k)。
这个严格的“小于”关系,正是构建有向图的依据。
问题转化
- 节点:将 \(n\) 个车站视为 \(n\) 个图节点。
- 边:如果根据上述分析,我们能确定
level(j) < level(k),就在节点 \(j\) 和节点 \(k\) 之间连接一条有向边 \(j \to k\)。 - 目标:建图完成后,问题就变成了:为图中所有节点分配一个正整数“级别”,使得对于任意边 \(j \to k\),都有
level(j) < level(k)。要求使用的不同级别数最少。
这等价于求这个有向无环图(DAG)中的最长路径所包含的节点数。因为最长路径上的每个节点都必须拥有一个比前驱节点更高的级别,所以最长路径的长度(节点数)就是我们所求的最小级别数。
算法步骤
建图
遍历 \(m\) 趟车次。对于每趟车,确定其停靠站集合 S 和行驶区间内未停靠的车站集合 U。
对于 U 中的任意车站 j 和 S 中的任意车站 k,都存在 level(j) < level(k) 的关系。因此,我们从 j 向 k 连一条有向边。
为了避免重复建边,可以用一个二维布尔数组 edge_added[j][k] 来记录边是否已添加。
拓扑排序求最长路
这是一个典型的在 DAG 上求最长路的问题,可以使用拓扑排序来解决。计算所有节点的入度 in_degree。将所有入度为 0 的节点放入一个队列,并将它们的初始级别(或路径长度)设为 1。
当队列不为空时,出队一个节点 u,其级别为 level_u。遍历 u 的所有邻接点 v:
- 将
v的入度减 1。 - 如果
v的入度变为 0,说明v的所有前驱节点都已被处理。此时,v的级别至少是level_u + 1。我们将v入队,并记录其级别。
在整个过程中,记录遇到的最大级别数,即为最终答案。
代码解析
import sys
from collections import deque
input = sys.stdin.readline
n, m = map(int, input().split())
MAXN = n + 5
# graph: 邻接表, in_degree: 入度数组
graph, in_degree = [[] for _ in range(MAXN)], [0] * MAXN
# edge_added: 用于防止重复建边
edge_added = [[False] * MAXN for _ in range(MAXN)]
# 1. 建图
for _ in range(m):
line = list(map(int, input().split()))
# s_i = line[0], 停靠站列表 tmp = line[1:]
tmp = line[1:]
if not tmp: continue
# cur: 停靠站集合(方便快速查找), start/end: 起始/终点站
cur, start, end = set(tmp), tmp[0], tmp[-1]
# 遍历所有在区间内但未停靠的车站 j
for j in range(start, end + 1):
if j not in cur:
# 对于每个未停靠的 j, 它和所有停靠的 k 构成约束关系
for k_node in tmp:
# level(j) < level(k_node) => j -> k_node
if not edge_added[j][k_node]:
graph[j].append(k_node)
in_degree[k_node] += 1
edge_added[j][k_node] = True
# 2. 拓扑排序求最长路
deq = deque()
# 将所有入度为 0 的节点入队,初始级别为 1
for i in range(1, n + 1):
if in_degree[i] == 0:
deq.append((i, 1)) # (节点, 级别)
ans = 1 # 至少有 1 级
while deq:
u, path_len = deq.popleft()
# 遍历 u 的后继节点 v
for v in graph[u]:
in_degree[v] -= 1
# 如果 v 的入度变为 0,说明其所有前驱已处理完毕
if in_degree[v] == 0:
new_path_len = path_len + 1
deq.append((v, new_path_len))
# 更新最大级别
ans = max(ans, new_path_len)
print(ans)

浙公网安备 33010602011771号