01 / 本课学习路线
本课学习路线
阅读与推演约 116 分钟,练习约 60 分钟,进阶练习另需约 25 分钟
02 / 学习目标与先修自测
学完本课你能做什么,以及开始前需要会什么
本课只有一个算法(Kahn)和一条判据(拓扑序长度 < 节点数 ⇔ 有环);三道题的差别全在「边怎么建」与「要输出什么」。
| 学完后能做的事 | 正文位置 | 检查方式 |
|---|---|---|
| 把「A 依赖 B」正确翻译成边的方向和入度变化 | 第 03 节 | 自查第 2 条 |
| 独立写出 Kahn 的完整流程并手算 4 节点例子 | 第 04 节 | 自查第 3 条、练习 4 |
| 说出判断有环的标准判据:拓扑序长度 < 节点数 | 第 04 节 | 自查第 4 条 |
| 两种异常分开判(入度 > 1 与环),通过(AC)P3757 | 第 05、08 节 | 必做任务 1 |
| 先判环再找头尾,处理重边、自环、不连通、编号不连续,通过 P3751 | 第 06、08 节 | 自查第 5 条、必做任务 2 |
先修自测:下面 5 题请先自己写答案,再展开对照。答不出的按括号里的位置补看再回来。本课假定你已完成「哈希计数」与上一课「深度优先搜索与回溯」。
| 题号 | 题目 | 补看位置 |
|---|---|---|
| 自测 1 | adj = defaultdict(list),adj["x"].append("y") 之后 adj["z"] 是什么?为什么不会抛 KeyError? | 字典与模块与 import |
| 自测 2 | "D->E".split("->") 得到什么? | 字符串常用方法 |
| 自测 3 | 节点编号不连续(如 1、3、7)时,怎样收集全部节点、怎样按从小到大输出? | 集合与列表常用操作 |
| 自测 4 | deque 的入队、出队各用哪个方法? | 模块 3 · 第 1 课 第 03 节 |
| 自测 5 | 行数未知、每行「主 次」两个词,怎样读到文件尾并跳过空行? | 标准输入输出与首次独立提交 |
展开先修自测答案
自测 1:[]。defaultdict(list) 在键不存在时自动建一个空列表,所以建图时不必先判断键在不在。
自测 2:["D", "E"]——按整个子串 -> 拆,不是按字符。
自测 3:用集合 nodes.add(a); nodes.add(b) 收齐两端;输出用 sorted(nodes)。开定长数组会因为编号 7 越界。
自测 4:append 入队、popleft 出队。
自测 5:for line in sys.stdin.read().split("\n"): parts = line.split(); if len(parts) == 2: …。P3757 就是这样读的。
03 / 概念与术语
有向边、入度、出度、拓扑序、环
先把题目里的一句话翻译成箭头,再统计入度——方向错了,后面全错。
| 术语 | 含义 | 代码写法 |
|---|---|---|
| 有向边 u → v | u 先于 v / u 是 v 的前置 / u 指向 v | adj[u].append(v) |
| 入度 | 指向该节点的边数:还有几个前置没完成 | indeg[v] += 1 |
| 出度 | 从该节点出发的边数 | len(adj[u]) |
| 拓扑序 | 把节点排成一列,所有边都从前指向后 | Kahn 的出队顺序 |
| Kahn 算法 | 反复取出入度 0 的节点,把它的出边删掉 | while q: u = q.popleft(); for v in adj[u]: indeg[v] -= 1 |
| 环 | 沿着箭头能回到起点;有环就不存在拓扑序 | len(order) < 节点数 |
| 自环 | u → u:自己依赖自己,直接构成环 | 该点入度天然 ≥ 1,永远进不了队列 |
| 重边 | 同一条边出现两次 | 按题意决定是否去重:P3757 要去重 |
| 题 | 题目里的一句话 | 边 | 谁的入度加一 |
|---|---|---|---|
| P3757 | 主告警 X,次告警 Y | X → Y | Y |
| P3751 | 「a b」表示 a 到 b 的路径 | a → b | b |
| P3750 | 「A->B」表示 A 依赖 B(先做 B) | B → A | A |
P3750 的箭头与输入写法相反:输入写 A->B,边却是 B→A。写代码前先把这一行写在注释里。
04 / Kahn 算法
逐步移除入度为 0 的节点;拓扑序长度 < 节点数 ⇔ 有环
4 个节点、边 0→1、0→2、1→3、2→3。按入度表逐步推演:
| 步骤 | 出队 | 入度 [0,1,2,3] | 队列 | 拓扑序 |
|---|---|---|---|---|
| 初始 | — | [0, 1, 1, 2] | [0] | [] |
| 1 | 0 | [0, 0, 0, 2](1、2 归零入队) | [1, 2] | [0] |
| 2 | 1 | [0, 0, 0, 1] | [2] | [0, 1] |
| 3 | 2 | [0, 0, 0, 0](3 归零入队) | [3] | [0, 1, 2] |
| 4 | 3 | [0, 0, 0, 0] | [] | [0, 1, 2, 3] |
处理数 4 == 节点数 4 → 无环。拓扑序不唯一:步骤 1 之后 1 与 2 谁先出队都合法([0, 2, 1, 3] 同样正确),这就是练习模板里的断言允许两种答案的原因。
补一条 3→0:有环时会怎样
入度变成 [1, 1, 1, 2]:没有任何入度为 0 的节点,队列一开始就是空的 循环体一次都不进,拓扑序长度 0 < 4 → 有环 判据只看长度:不要用「队列是否空」「是否停住」这类中间状态判断
拓扑排序练习模板
Pythonimport sys
from collections import defaultdict, deque
def main() -> None:
# 待完成 1:按题目要求的格式读入边;写下方向约定——「A 依赖 B」是哪个方向
edges = []
adj = defaultdict(list)
indeg = defaultdict(int)
nodes = set()
for a, b in edges:
# 待完成 2:建边、重边去重、自环立即判环、入度统计
...
q = deque(sorted(n for n in nodes if indeg[n] == 0))
order = []
while q:
u = q.popleft()
order.append(u)
for v in adj[u]:
indeg[v] -= 1
# 待完成 3:什么时候才让 v 入队
...
# 待完成 4:用 len(order) 和 len(nodes) 判环,并按题目要求输出
...
main()建图 + Kahn:时间 O(V+E)、空间 O(V+E)。P3750 的并列规则不是换容器,而是每一轮把新释放的任务排序后一起执行(第 07 节)。
05 / 两种异常分开判:P3757
入度 > 1 是多个主告警,拓扑序不完整是环;同时存在输出 1001
P3757「主次关联成环警告」:每行「主告警 次告警」(行数不给);情况 1:同一个告警有多个主告警 → 输出 [1001,(b,d,e)](按字母序);情况 2:关联成环 → [1002,cycle];都没有 → [1003,Verified];同时存在输出检查码最小的。题面示例:a b / c b → [1001,(b)];a b / b a → [1002,cycle]。
| 输入 | 去重后的边 | 入度 > 1 的节点 | Kahn 拓扑序长度 / 节点数 | 输出 |
|---|---|---|---|---|
| a b / c b | a→b, c→b | b(入度 2) | —(情况 1 已成立,不再判环) | [1001,(b)] |
| a b / b a | a→b, b→a | 无 | 0 / 2 | [1002,cycle] |
| a b / b c | a→b, b→c | 无 | 3 / 3 | [1003,Verified] |
| a b / a b / b c | a→b, b→c(重复行只算一条) | 无 | 3 / 3 | [1003,Verified] |
| a b / c b / b a | a→b, c→b, b→a | b | — | [1001,(b)](同时有环,仍输出 1001) |
自环 a a:a 的入度 1,不算多个主告警,但 a 永远进不了队列 → [1002,cycle]。重复行不去重会把 a b / a b 的 b 算成入度 2 而误报 1001。
补充学习(选学)「多个主告警」和环是两回事约 4 分钟P3757 的两种异常为什么要分开判
一个节点有多个父节点(同一个告警有两个主告警)不构成环:A→C、B→C 完全无环,但按 P3757 的业务规则仍是异常。反过来 A→B→A 是环,但每个点都只有一个父节点。两种异常判据不同:多个父节点看「入度 > 1」(按去重后的边算),环看「拓扑序长度 < 节点数」——分开判、按题目要求的顺序输出。
06 / 头尾节点:P3751
先判环,再取入度 0 为头、出度 0 为尾
P3751「查找有向网络的头尾节点」:第一行 N,第二行 2N 个整数,每两个是一条边 a b(a → b);输出头节点(入度 0,题目保证唯一)和全部尾节点(出度 0,从小到大);有环输出 -1。题面示例:4 / 1 2 1 3 2 4 3 4 → 1 4。
| 节点 | 入度 | 出度 | 角色 |
|---|---|---|---|
| 1 | 0 | 2(→2、→3) | 头 |
| 2 | 1 | 1 | — |
| 3 | 1 | 1 | — |
| 4 | 2 | 0 | 尾 |
Kahn 拓扑序 [1, 2, 3, 4] 长度 4 = 节点数 → 无环 → 输出 1 4。节点编号不连续(如 1 2 1 3 3 5)用集合收齐,尾节点 2、5 从小到大输出:1 2 5。
有环:3 / 1 2 2 3 3 1
入度全为 1,没有入度 0 的节点 → 拓扑序长度 0 < 3 → 输出 -1 为什么先判环:有环时「头节点」根本不存在,先找头会取到空列表
07 / 并列规则:P3750
按轮排序:同一轮新可执行的任务按名字排序后一起执行
P3750「启动多任务排序」:一行若干个 A->B(A 依赖 B),空格分隔;没有依赖的任务立刻执行,同时有多个可执行的按名字字母序;输出执行顺序。题面示例:B->A C->A D->B D->C D->E → A E B C D;A->B C->B → B A C。
| 轮 | 本轮执行(排序后) | 释放的任务 | 说明 |
|---|---|---|---|
| 0 | A E | B、C(A 完成后入度归零) | 一开始入度 0 的是 A 与 E,排序后同时执行 |
| 1 | B C | D(B、C、E 都完成) | 同一轮新释放的按名字排序 |
| 2 | D | — |
输出 A E B C D,与题面一致。
不能换成全局最小堆——题面示例本身就是反例
用最小堆维护所有可执行任务:先出 A,释放 B、C,堆里变成 {B, C, E},接着出 B、C,再出 E,最后 D → A B C E D,与题面 A E B C D 不符。题意是「同时可执行的一起做」:E 与 A 是同一轮释放的,必须紧接着 A 执行,不能被后来释放的 B、C 插队。正确做法是逐轮处理:这一轮出队的任务释放出来的新任务排序后作为下一轮。
再算一组:B->A C->A Z->Y
入度 0:A、Y → 排序 → 轮 0 执行 A Y A 释放 B、C;Y 释放 Z → 排序 → 轮 1 执行 B C Z 输出 A Y B C Z(最小堆会得到 A B C Y Z)
08 / 从步骤到程序
三道题的完整参考程序,每一步落在哪几行
先自己写完并提交一次,再展开对照。
| 题 | 建图 | 判定 | 输出 |
|---|---|---|---|
| P3757 | edges = set() 去重;adj[a].append(b);indeg[b] += 1 | 先 indeg[v] > 1,再 Kahn 计数 done < len(nodes) | [1001,(…)] / [1002,cycle] / [1003,Verified] |
| P3751 | 每两个数一条边;集合收节点 | Kahn 判环 → -1 | 入度 0 为头;not adj[v] 为尾,sorted |
| P3750 | a, b = token.split("->");adj[b].append(a);indeg[a] += 1 | — | 初始入度 0 排序;每轮 freed.sort() 后追加 |
展开完整参考程序 1:P3757 主次关联成环警告(先自己写完并提交一次,再展开对照)
完整程序:P3757(标准输入 → 标准输出)
Pythonimport sys
from collections import defaultdict, deque
edges = set() # 同一条「主 次」关系出现两次只算一条
for line in sys.stdin.read().split("\n"):
parts = line.split()
if len(parts) == 2:
edges.add((parts[0], parts[1])) # 主告警 → 次告警
adj = defaultdict(list)
indeg = defaultdict(int)
nodes = set()
for a, b in edges:
adj[a].append(b)
indeg[b] += 1
nodes.add(a)
nodes.add(b)
multi = sorted(v for v in nodes if indeg[v] > 1) # 情况 1:同一个次告警有多个主告警
if multi:
print(f"[1001,({','.join(multi)})]") # 两种情况同时存在时输出检查码最小的 1001
else:
q = deque(v for v in nodes if indeg[v] == 0)
done = 0
while q: # Kahn:拓扑序长度 < 节点数 ⇔ 有环
u = q.popleft()
done += 1
for v in adj[u]:
indeg[v] -= 1
if indeg[v] == 0:
q.append(v)
print("[1002,cycle]" if done < len(nodes) else "[1003,Verified]")自测建议:题面两组示例、第 05 节三个自拟输入。重复行按题意「同一个告警是否存在多个主告警」去重;题目页参考题解没有单独处理重复行,判题数据以题目页为准。
展开完整参考程序 2:P3751 查找有向网络的头尾节点
完整程序:P3751(标准输入 → 标准输出)
Pythonimport sys
from collections import defaultdict, deque
data = sys.stdin.read().split()
n = int(data[0])
vals = [int(x) for x in data[1:1 + 2 * n]]
adj = defaultdict(list)
indeg = defaultdict(int)
nodes = set()
for i in range(0, 2 * n, 2):
a, b = vals[i], vals[i + 1] # a → b
adj[a].append(b)
indeg[b] += 1
nodes.add(a)
nodes.add(b)
q = deque(v for v in nodes if indeg[v] == 0)
done = 0
indeg_left = dict(indeg)
while q:
u = q.popleft()
done += 1
for v in adj[u]:
indeg_left[v] -= 1
if indeg_left[v] == 0:
q.append(v)
if done < len(nodes): # 有环
print(-1)
else:
heads = [v for v in nodes if indeg[v] == 0] # 入度 0 = 头(题目保证唯一)
tails = sorted(v for v in nodes if not adj[v]) # 出度 0 = 尾(可能多个,从小到大)
print(heads[0], *tails)自测建议:题面示例(输出 1 4)、有环(输出 -1)、编号不连续且多个尾(3 / 1 2 1 3 3 5 → 1 2 5)。题面写 N ≥ 0,但 N = 0(没有任何边)时「唯一头节点」无从谈起,本程序不处理这种输入;题目页若有此类用例以题目页为准。
展开完整参考程序 3:P3750 启动多任务排序(进阶)
完整程序:P3750(标准输入 → 标准输出)
Pythonimport sys
from collections import defaultdict, deque
pairs = sys.stdin.read().split()
adj = defaultdict(list) # 被依赖者 → 依赖它的任务
indeg = defaultdict(int)
nodes = set()
for token in pairs:
a, b = token.split("->") # A->B:A 依赖 B,先做 B 再做 A,边 B → A
adj[b].append(a)
indeg[a] += 1
nodes.add(a)
nodes.add(b)
order = sorted(v for v in nodes if indeg[v] == 0) # 一开始就能执行的任务,按名字排序
q = deque(order)
while q:
freed = [] # 这一轮执行完后新变成可执行的任务
for _ in range(len(q)): # 同一轮里的任务同时执行
u = q.popleft()
for v in adj[u]:
indeg[v] -= 1
if indeg[v] == 0:
freed.append(v)
freed.sort() # 同一轮新可执行的任务按字母序
order.extend(freed)
q.extend(freed)
print(*order)自测建议:题面两组示例(A E B C D、B A C)、第 07 节第二组(A Y B C Z)。
09 / 边界、反例与复杂度
错误做法在具体输入上各输出什么
下面每一行都是一个具体的错误程序,给出输入、错误输出与正确输出;把参考程序改成对应写法就能复现。
| 错误做法 | 输入 | 错误输出 | 正确输出 | 判题结果 |
|---|---|---|---|---|
| P3750 边方向建反(A->B 建成 A→B) | A->B C->B | A C B | B A C | 答案错误(WA) |
| P3750 用全局最小堆代替逐轮排序 | 题面示例 1 | A B C E D | A E B C D | 答案错误(WA) |
| P3757 重复行不去重 | a b / a b / b c | [1001,(b)] | [1003,Verified] | 答案错误(WA) |
| P3757 先判环再判多主告警 | a b / c b / b a | [1002,cycle] | [1001,(b)] | 答案错误(WA) |
| P3751 不判环直接找头 | 3 / 1 2 2 3 3 1 | 头节点列表为空,抛出 IndexError | -1 | 运行错误(RE) |
| P3751 尾节点不排序 | 3 / 1 3 1 5 1 7 | 顺序随输入(如 1 7 5 3) | 1 3 5 7 | 答案错误(WA) |
| 节点编号不连续却开定长数组 | 1 / 7 9 | 下标越界 | 7 9 | 运行错误(RE) |
| 每轮线性扫描找入度 0 的点 | V = 10⁵ | 结果正确但 O(V²) | 同左 | 超时(TLE) |
| 做法 | 时间 | 说明 |
|---|---|---|
| 建图 + Kahn | O(V + E) | 每条边被「减入度」一次 |
| 逐轮排序(P3750) | O(V + E + V log V) | 每个任务只被排序一次 |
| 每轮扫描全部节点找入度 0 | O(V²) | 10⁵ 节点时超时 |
10 / 渐进练习与参考答案
跟做 → 改一个条件 → 独立实现 → 迁移
每题先在纸上或文件里做完,再展开答案。
练习 1(跟做):按第 04 节表的格式,对 5 节点、边 0→2、1→2、2→3、2→4、3→4 逐步写出入度、队列与拓扑序(队列按编号从小到大入队)。
展开练习 1 答案
初始入度 [0, 0, 2, 1, 2],队列 [0, 1]。出 0:2 的入度 2→1;出 1:2 归零入队;出 2:3 归零、4 的入度 2→1;出 3:4 归零;出 4。拓扑序 [0, 1, 2, 3, 4],长度 5 = 节点数 → 无环。
练习 2(改一个条件):P3751 若题目允许多个头节点,输出怎么改?对 3 / 1 3 2 3 3 4 写出输出。
展开练习 2 答案
把「取 heads[0]」改成「sorted(heads) 全部输出」:入度 0 的是 1 和 2,尾是 4 → 1 2 4。程序其余不变;本题原规则保证只有一个头,所以取 heads[0]。
练习 3(改一个条件):P3750 改成「同时可执行的按名字逆序」,改哪一行?题面示例 1 的输出是什么?
展开练习 3 答案
两处排序都加 reverse=True:初始 sorted(..., reverse=True) 与每轮 freed.sort(reverse=True)。示例 1:轮 0 E A;轮 1 C B;轮 2 D → E A C B D。逐轮结构不变。
练习 4(独立实现):完成「代码自测」的 topo_order,再加两条断言:补边 3→0 后应为 None;3 个节点无边应为 [0, 1, 2]。
展开练习 4 答案
topo_order 的参考实现(自带断言)
Pythonfrom collections import deque, defaultdict
def topo_order(n, edges):
adj = defaultdict(list)
indeg = [0] * n
for a, b in edges: # a → b:先 a 后 b
adj[a].append(b)
indeg[b] += 1
q = deque(v for v in range(n) if indeg[v] == 0)
order = []
while q:
u = q.popleft()
order.append(u)
for v in adj[u]:
indeg[v] -= 1
if indeg[v] == 0: # 减到 0 的那一刻入队
q.append(v)
return order if len(order) == n else None # 拓扑序长度 < 节点数 ⇔ 有环
assert topo_order(4, [(0, 1), (0, 2), (1, 3), (2, 3)]) in ([0, 1, 2, 3], [0, 2, 1, 3])
assert topo_order(2, [(0, 1), (1, 0)]) is None # 成环
assert topo_order(1, [(0, 0)]) is None # 自环也是环:入度天然 ≥ 1,永远进不了队列
assert topo_order(4, [(0, 1), (0, 2), (1, 3), (2, 3), (3, 0)]) is None # 补一条 3→0 后有环
assert topo_order(3, []) == [0, 1, 2] # 无边:全部入度 0五条断言覆盖:4 节点两种合法序、二元环、自环、补边成环、无边。自环不需要特判:入度天然 ≥ 1,永远进不了队列,长度判据一样能抓住。
练习 5(迁移):不运行程序,对 P3757 输入 x y / y z / p q / q p 写出输出,并说明两个互不相连的部分为什么不影响判环。
展开练习 5 答案
入度:y 1、z 1、q 1、p 1,没有 > 1 的。Kahn:只有 x 入度 0 → 出 x、y、z 共 3 个,p 与 q 互相等待永远进不了队列 → 拓扑序长度 3 < 节点数 5 → [1002,cycle]。判据按整张图的节点数计,不管有几个连通部分。
11 / 读题要求与复习自评
三道题的要求对照,以及完成本课之后怎么复习
提交前把下表过一遍;题目页的题面与样例是最终依据。
| 项目 | P3757 | P3751 | P3750 |
|---|---|---|---|
| 输入 | 若干行「主 次」 | N;2N 个整数 | 一行若干 A->B |
| 边 | 主 → 次 | a → b | B → A(与写法相反) |
| 输出 | [1001,(…)] / [1002,cycle] / [1003,Verified] | 头 尾…;有环 -1 | 执行顺序,空格分隔 |
| 并列 | 1001 内按字母序;两种异常同时存在输出 1001 | 尾从小到大 | 同一轮按名字字母序 |
| 样例 | a b / c b → [1001,(b)] | 4 / 1 2 1 3 2 4 3 4 → 1 4 | B->A C->A D->B D->C D->E → A E B C D |
需要对照解法时,展开本课第 08 节的完整参考程序;题目页另有思路与参考代码,可在题目页查看。完成条件:本课算完成 = P3757、P3751 两道必做题都通过判题,并勾选全部六条「学习完成检查」;六条检查是自评,勾选不改变题目的通过(AC)状态;进阶练习与复习题单独统计,不影响完成状态。复习时用三个问题自测:① 不看正文,说出「A->B 表示 A 依赖 B」该建哪个方向的边;② 不看表格,重推第 04 节 4 节点的入度表;③ 说出 P3750 为什么不能用全局最小堆。答不出哪一条,就回到对应的节重读,再做第 10 节对应的练习。
12 / 练习
按顺序完成本课的任务
必做题已通过 0/2 道;进阶练习已通过 0/1 道
拓扑序与判环(topo_order)
代码自测自主练习练习重点:建邻接表 + 入度 + Kahn + 拓扑序长度(len)判环;预计用时:15 分钟
完成标准:能解释为什么自环用例也会被 len(order)<n 抓住
需要时查看提示
自环让该点入度天然 ≥ 1 且永远减不到 0,进不了队列——不需要特判也会被长度判据抓住;但显式特判能更早报错。断言 1 的两种合法序说明拓扑序不唯一。参考实现在第 10 节练习 4 的展开区。
自测代码(复制到你的代码文件中运行,检查输出是否一致)
from collections import deque, defaultdict
def topo_order(n, edges):
# 你来写:edges 里 (a, b) 表示 a → b(先 a 后 b)。
# 返回拓扑序列表;有环返回 None
...
assert topo_order(4, [(0, 1), (0, 2), (1, 3), (2, 3)]) in ([0, 1, 2, 3], [0, 2, 1, 3])
assert topo_order(2, [(0, 1), (1, 0)]) is None # 成环
assert topo_order(1, [(0, 0)]) is None # 自环也是环P3757 · 主次关联成环警告
必做任务 1练习重点:两种异常分开判:一个节点有多个父节点(入度>1)+ 关联成环;预计用时:25 分钟
完成标准:能举出「有多个父节点但无环」和「有环但没有多个父节点」各一个例子
需要时查看提示
重边先去重再统计入度,否则同一条关联出现两次会把入度错误地重复计数为 2、误报多个主告警。先判 1001 再判环:两种异常同时存在时输出检查码最小的。第 05 节把题面示例与自拟输入逐个判定。
P3751 · 查找有向网络的头尾节点
必做任务 2练习重点:入度 0 是头、出度 0 是尾;有环输出 -1;预计用时:20 分钟
完成标准:能说出为什么判环要在找头尾之前做
需要时查看提示
先跑 Kahn 判环(拓扑序列表(order)长度 len(order)<n → 输出 -1),再统计入度 0 和出度 0。尾节点可能多个,从小到大输出。节点编号不一定连续,用集合收节点。第 06 节有题面示例的入度出度表。
P3750 · 启动多任务排序
进阶练习 1进阶练习练习重点:并列规则按轮:同一轮新可执行的任务按名字排序后一起执行;预计用时:25 分钟
完成标准:能用题面示例说明为什么全局最小堆会得到错误顺序
需要时查看提示
每一轮把队列里的任务全部出队,收集新释放的任务,排序后作为下一轮——不要用全局最小堆(题面示例 1 会变成 A B C E D)。边方向与输入写法相反:A->B 建 B→A。任务名是字符串节点,用默认字典(defaultdict)建图最方便。第 07 节有逐轮表。
提交结果
提交结果说明与处理方法
- WA
答案错误
优先检查三处:「A 依赖 B」方向建反、重边没去重导致入度重复计数、P3750 用了全局最小堆而不是逐轮排序——第 09 节的表给出了每种错误的具体输出
- RE
运行错误
节点编号不连续却开了定长数组;有环时先找头节点会取到空列表;空输入没处理
- TLE
超时
每轮线性扫描找入度 0 的点(O(V²))——入度减到 0 的那一刻入队才是 O(V+E)
- AC
通过
再测自环、两个连通分量、单节点无边三个边界;口述判环的判据
13 / 学习完成检查
本课学习完成检查
完成本课需要:必做题全部通过,并勾选本课的全部学习完成检查;进阶练习、基础加练与复习题单独统计,不影响完成状态。登录后,勾选记录会保存到账号,并更新课程总览的完成状态。