Luogu P1983 [NOIP 2013 普及组] 车站分级

核心思路

本题要求解最少需要划分的车站级别数。我们可以将问题转化为图论中的最长路问题。关键在于分析题目给出的约束条件:对于任意一趟车,如果它停靠了车站 \(x\),那么在它行驶区间内所有级别不低于 \(x\) 的车站,它都必须停靠。

反过来思考这个条件:对于一趟车,如果在其运行区间(始发站到终点站)内,有一个车站 \(j\) 没有停靠,而车站 \(k\) 停靠了,那么我们可以推断出车站 \(j\) 的级别一定低于车站 \(k\) 的级别。即 level(j) < level(k)

这个严格的“小于”关系,正是构建有向图的依据。

问题转化

  1. 节点:将 \(n\) 个车站视为 \(n\) 个图节点。
  2. :如果根据上述分析,我们能确定 level(j) < level(k),就在节点 \(j\) 和节点 \(k\) 之间连接一条有向边 \(j \to k\)
  3. 目标:建图完成后,问题就变成了:为图中所有节点分配一个正整数“级别”,使得对于任意边 \(j \to k\),都有 level(j) < level(k)。要求使用的不同级别数最少。

这等价于求这个有向无环图(DAG)中的最长路径所包含的节点数。因为最长路径上的每个节点都必须拥有一个比前驱节点更高的级别,所以最长路径的长度(节点数)就是我们所求的最小级别数。

算法步骤

建图

遍历 \(m\) 趟车次。对于每趟车,确定其停靠站集合 S 和行驶区间内未停靠的车站集合 U

对于 U 中的任意车站 jS 中的任意车站 k,都存在 level(j) < level(k) 的关系。因此,我们从 jk 连一条有向边。

为了避免重复建边,可以用一个二维布尔数组 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)
posted @ 2025-08-05 21:24  AFewMoon  阅读(7)  评论(0)    收藏  举报