01 / 本课学习路线
本课学习路线
阅读与推演约 112 分钟,练习约 65 分钟,进阶练习另需约 25 分钟
02 / 学习目标与先修自测
学完本课你能做什么,以及开始前需要会什么
做规则模拟题,先说明每条规则由哪一步实现,再检查这些步骤组合后的行为。条件判断、循环、计数和排序都可能承载规则,不能用分支数量判断实现是否完整。写完后为每条规则准备检查用例。
| 学完后能做的事 | 正文位置 | 检查方式 |
|---|---|---|
| 说出 AI042 的并列规则和不重叠合并规则 | 第 05、06 节 | 自查第 2 条、代码自测 |
| 举例说明 aaa 这类重叠情况怎么计数 | 第 05 节 | 自查第 3 条、练习 5 |
| 每条规则都有对应实现,写完能逐条核对 | 第 05、08 节 | 自查第 4 条 |
| 说出 BPE 增量更新时哪些统计会变(进阶方向) | 补充学习 | 自查第 5 条 |
| 通过 P2488、AI042 | 第 08 节 | 必做任务 1、2 |
先修自测:下面 5 题请先自己写答案,再展开对照。答不出的按括号里的位置补看再回来。本课假定你已完成模块 1 的「字符串规则模拟」和「计数与并列规则」。
| 题号 | 题目 | 补看位置 |
|---|---|---|
| 自测 1 | "aaa" 里相邻的字符对 (a,a) 有几个?"abab" 里 (a,b) 有几个、(b,a) 有几个? | 字符串 |
| 自测 2 | "a,b,,c".replace(",", " ").split() 得到什么? | 字符串规则模拟第 05 节 |
| 自测 3 | s[i:j] in words(words 是集合)平均是多快?为什么词库用集合不用列表? | 集合 |
| 自测 4 | 用字典给键 (a,b) 累加 3:count[("a", "b")] = count.get(("a", "b"), 0) + 3,元组能当字典键吗? | 字典 |
| 自测 5 | ("ab", "c") < ("b", "a") 是真是假?先比什么? | 计数与并列规则第 04 节 |
展开先修自测答案
自测 1:2 个(位置 0-1 与 1-2,重叠也算);(a,b) 2 个、(b,a) 1 个。AI042 的加权统计按这个数法。
自测 2:['a', 'b', 'c']——不带参数的 split() 会吞掉连续空白,所以连续标点不会产生空段。P2488 的标点断句就是这样做。
自测 3:平均 O(1)。列表是逐个比较的 O(m),词库上万时每次匹配都慢一个数量级。
自测 4:能——元组不可变,可以做键。统计相邻符号对就用 (A, B) 元组当键。
自测 5:真。先比第 1 项 "ab" < "b"(字符串字典序,a 排在 b 前),第 1 项不同就不看第 2 项。AI042 并列规则的比较对象是符号串,不是首字符。
03 / 概念与术语
规则清单、最长匹配、符号对、加权计数、不重叠合并
两道必做题都需要先读清规则,再逐步实现;BPE 还要在每轮合并后重新统计符号对。
| 术语 | 含义 | 代码里的位置 |
|---|---|---|
| 规则清单 | 列出题目规则,标明对应的实现步骤与检查用例;规则数不等于分支数 | 函数开头的注释 |
| 顺序优先最长匹配 | 从当前位置起、长度从大到小试,命中即前进(不回头找全局最优) | while j > i and seg[i:j] not in words: j -= 1 |
| 符号 | BPE 里的基本单位:开始是单个字符,合并后变成多字符串 | syms 列表的元素 |
| 相邻符号对 | 一个词里相邻的两个符号 (A, B);重叠也各算一次 | (syms[i], syms[i+1]) |
| 加权计数 | 每个相邻对的次数乘以词频后累加 | count[pair] += f |
| 并列规则 | 次数相同时先比 A 再比 B,按字符串字典序取小 | min(count, key=lambda p: (-count[p], p[0], p[1])) |
| 不重叠合并 | 每个词从左到右扫,遇到 (A, B) 就合成 AB 并跳两格,用过的符号不再参与本轮 | i += 2 |
补充学习(选学)增量更新:BPE 的工程方向约 5 分钟合并一个相邻符号对后,哪些统计需要变
AI042 规模小到每轮全量重算即可,但真实分词器的语料是千万级:合并 (a,b) 为 ab 后,只有合并点两侧的统计会变——(x,a)、(b,y) 减少,(x,ab)、(ab,y) 增加。增量维护把每轮从 O(总长) 降到 O(受影响的相邻符号对数)。这一优化说明了真实分词器如何避免每轮扫描全部语料。
补充学习(选学)进阶方向:字典树(Trie)与 KMP约 4 分钟词库更大、模式更长时的两种方法
逐长度尝试最长匹配的复杂度是 O(len × max_len)。词库很大时可以把词库建成字典树(Trie),从当前位置沿树走、记录最后一次命中;单模式串匹配可以使用 KMP 字符串匹配算法,它利用已匹配部分的前后缀信息(失配数组)减少重复比较。这两项是本模块的选学内容:先完成未通过题目的订正,再根据时间学习;阶段测验不依赖它们。
04 / P2488 中文分词模拟器
标点断句 + 顺序优先最长匹配 + 未命中按单个字母
P2488「中文分词模拟器」:第一行待分词的字符串(只含小写字母与逗号、分号、句号,长度 < 256),第二行逗号分隔的词库(词数 < 100000);标点只断句不成词,按「顺序优先且最长匹配」分词,不重叠;未命中的字母单独成词;输出逗号分隔的分词结果。题面示例 1:ilovechina + 词库 i,love,china,ch,na,… → i,love,china;示例 2:iat → i,a,t;示例 3:ilovechina,thewordisbeautiful → i,love,china,the,word,is,beauti,ful。
| 位置 i | 剩余串 | 依次尝试 | 命中 | 下一位置 |
|---|---|---|---|---|
| 0 | ilovechina | ilove ✗ ilov ✗ ilo ✗ il ✗ i ✓ | i | 1 |
| 1 | lovechina | lovec ✗ love ✓ | love | 5 |
| 5 | china | china ✓ | china | 10(结束) |
输出 i,love,china。题面说明里的例子:词库换成含 ilove 的 i,ilove,lo,love,ch,china,lovechina → 位置 0 试到 ilove 就命中,输出 ilove,china——「i 优先于 lovechina」和「ilove 优先于 i」都由这一条规则得到。
| 输入 | 断句后 | 逐段结果 |
|---|---|---|
| iat | iat | i ✓;a:at ✗ a ✗ → 单字母 a;t → 单字母 t |
| ilovechina,thewordisbeautiful | ilovechina / thewordisbeautiful | i love china / the word is beauti ful(beauti 长于 the、is,先命中) |
示例 3 的第二段:位置 0 试 thewo ✗ … the ✓;接着 word;is;beauti(长度 6,词库最长词 beauti 长 6);ful。逗号只断句,输出里不出现它。题目页参考题解把三种标点替换成空格再 split,与这里一致。
05 / AI042 四条规则
逐条实现:统计、取最高、合并、提前结束
AI042「BPE 子词合并训练」:第一行两个整数 w R(词数与轮数,都不超过 50);随后 w 行,每行「词频 词」(词只含小写字母、长度不超过 30,词频不超过 1000)。每轮加权统计相邻符号对、取次数最高的一对合并、把每个词里的这一对从左到右不重叠地合成一个符号;统计不到任何相邻对时提前结束。输出:先每轮一行「A+B 次数」(提前结束时行数少于 R),再按输入顺序输出各词最终切分(符号之间单个空格)。样例 1:2 2 / 3 abab / 2 abc → a+b 8 / ab+ab 3 / abab / ab c;样例 2:2 1 / 2 ab / 2 cd → a+b 2 / ab / c d(第 06 节逐轮推演)。
| 规则 | 含义 | 实现位置(参考程序) | 最小检查用例 |
|---|---|---|---|
| ① 加权统计相邻对 | 每个词里每一对相邻符号计一次(重叠也各计),乘词频 | for i in range(len(syms) - 1): count[pair] += f | aaa ×1 → (a,a) = 2 |
| ② 取最高,并列取字典序小 | 先比 A 再比 B,按字符串字典序 | min(count, key=(-次数, A, B)) | ab ×2、cd ×2 → (a,b) |
| ③ 从左到右不重叠合并 | 扫到 (A,B) 就写 AB 并跳两格;否则写当前符号跳一格 | i += 2 / i += 1 | aaa 合并 (a,a) → aa a |
| ④ 提前结束 | 没有任何相邻对(每个词都只剩一个符号)就停止,不补空轮 | if not count: break | a ×5、b ×1、R = 3 → 0 行 |
三个需要核对的实现细节
① aaa 的 (a,a) 必须计 2 次——「词内去重」在重叠情况上直接出错;② 合并从左到右不重叠:aaa → aa a,不是 aa aa a;③ 提前结束后不再产生输出行——多输出一行空轮会使行数不符合题目要求,判为答案错误。
06 / AI042 逐轮表
样例 1 两轮合并、样例 2 并列、提前结束
样例 1:3 个 abab、2 个 abc,R = 2。
| 轮 | 加权统计(按次数降序) | 胜出对 | 合并后(词 × 词频) | 输出行 |
|---|---|---|---|---|
| 1 | (a,b) 3×2 + 2×1 = 8;(b,a) 3;(b,c) 2 | (a,b) | ab ab ×3;ab c ×2 | a+b 8 |
| 2 | (ab,ab) 3;(ab,c) 2 | (ab,ab) | abab ×3;ab c ×2 | ab+ab 3 |
最终切分:abab → abab;abc → ab c。若 R = 3,第 3 轮统计只剩 (ab,c) 2 → 合并得 abc,输出「ab+c 2」;R = 5 时第 4 轮统计为空,提前结束,共 3 行。
| 加权统计 | 比较 | 胜出 | 输出 |
|---|---|---|---|
| (a,b) 2;(c,d) 2 | 次数相同 → 比 A:"a" < "c" | (a,b) | a+b 2;最终 ab → ab,cd → c d |
比较的对象是符号串:(ab,c) 与 (b,a) 并列时,"ab" < "b",取 (ab,c)。按「先出现」取并列会在词的输入顺序变化时给出不同答案,是错误做法。
07 / P3756 数据单元的变量替换
引用展开:至多一个 <X>,按依赖递归;五类异常输出 −1
P3756「数据单元的变量替换」(进阶):一行 CSV,最多 26 个单元格,编号 A..Z;单元格内容含字母数字和至多一个 <X> 引用;引用可指向后面的格子;输出全部展开后的内容(逗号分隔),处理出错输出 −1。题面没有给示例,下面的例子来自题目页参考题解与本课自算。
| 单元格 | 原内容 | 拆成(前, 引用, 后) | 展开 |
|---|---|---|---|
| A | 1 | (1, —, —) | 1 |
| B | 2<A>00 | (2, A, 00) | 2 + 1 + 00 = 2100 |
| C | 3<B>4 | (3, B, 4) | 3 + 2100 + 4 = 321004 |
输出 1,2100,321004。引用可以指向后面的格子:x<B>y,z → 展开 A 时先展开 B → xzy,z。
| 异常 | 例子 | 在哪一步发现 |
|---|---|---|
| 多重引用 | <A><B>,x | 解析:< 或 > 多于一个 |
| 括号不配对 | p<A | 解析:只有 < 没有 > |
| 引用不存在的单元格 | <C>,x(只有 A、B) | 解析:引用名不在编号集合里 |
| 自引用 | <A> | 展开:展开 A 时又要展开 A |
| 循环引用 | a<B>,b<A> | 展开:A → B → A |
自引用和循环引用用「展开中」标记发现:进入一个单元格时标记,展开完成前再次进入就是环。题目页参考题解用拓扑排序判环,结果相同。
08 / 从规则到程序
参考实现与完整程序,每一步落在哪几行
先自己写完并提交一次,再展开对照。AI042 先给函数级参考实现(便于用断言测试),再给完整程序。
| 步骤 | P2488 | AI042 | P3756 |
|---|---|---|---|
| 读入 / 拆分 | 标点替换成空格再 split();词库入集合 | 词拆成单字符符号列表 | 按逗号拆格;每格解析出 (前, 引用, 后) |
| 主循环 | 位置 i 从 0 起,长度从大到小试 | 每轮:统计 → 取最高 → 合并 | 递归展开 + 记忆化 |
| 边界 / 异常 | 未命中按单个字母 | 统计为空提前结束 | 五类异常 → −1 |
| 输出 | 逗号拼接 | 先每轮一行,再各词切分 | 逗号拼接或 −1 |
AI042 主循环代码框架
Pythonimport sys
def main() -> None:
# AI042 代码框架:words = [(freq, [符号列表]), ...]
for _ in range(R):
# 待完成 1:加权统计相邻符号对——aaa 的 (a,a) 计 2 次,权重乘词频
# 待完成 2:取出现次数最高的符号对;并列取字典序小(先比 A 再比 B)
# 待完成 3:每个词从左到右不重叠合并;统计不到任何符号对时提前结束
...
# 待完成 4:先输出每轮「A+B count」,再按输入顺序输出各词最终切分
main()规模很小(w ≤ 50、R ≤ 50、词长 ≤ 30),考查的是规则的精确实现,不是效率。代码框架里每一处需要你补写的注释都对应题目中的一条规则。
展开参考实现:AI042 的合并训练函数(先自己写完再对照)
bpe_train 的参考实现(自带断言)
Pythondef bpe_train(words, rounds):
# words: [(词, 词频)];每轮:加权统计相邻符号对 → 取最高(并列取字典序小)→ 每词从左到右不重叠合并;统计不到相邻对就提前结束
seqs = [(list(w), f) for w, f in words] # 每个词拆成单字符符号序列
merges = []
for _ in range(rounds):
count = {}
for syms, f in seqs:
for i in range(len(syms) - 1): # 重叠也计:aaa 的 (a,a) 计 2 次
pair = (syms[i], syms[i + 1])
count[pair] = count.get(pair, 0) + f
if not count:
break # 没有任何相邻对:提前结束
best = min(count, key=lambda p: (-count[p], p[0], p[1])) # 次数最高;并列先比 A 再比 B(字符串字典序)
merges.append(f"{best[0]}+{best[1]} {count[best]}")
new_seqs = []
for syms, f in seqs: # 从左到右不重叠合并
out, i = [], 0
while i < len(syms):
if i + 1 < len(syms) and (syms[i], syms[i + 1]) == best:
out.append(syms[i] + syms[i + 1])
i += 2
else:
out.append(syms[i])
i += 1
new_seqs.append((out, f))
seqs = new_seqs
return merges, [" ".join(syms) for syms, _ in seqs]
m, s = bpe_train([("abab", 3), ("abc", 2)], 2)
assert m == ["a+b 8", "ab+ab 3"] and s == ["abab", "ab c"] # 样例 1(第 05 节两轮表)
m, s = bpe_train([("ab", 2), ("cd", 2)], 1)
assert m == ["a+b 2"] and s == ["ab", "c d"] # 样例 2:并列取字典序小
m, s = bpe_train([("aaa", 1)], 1)
assert m == ["a+a 2"] and s == ["aa a"] # 重叠计 2 次;合并从左不重叠
m, s = bpe_train([("a", 5), ("b", 1)], 3)
assert m == [] and s == ["a", "b"] # 统计不到相邻对:提前结束,不补空轮
m, s = bpe_train([("abab", 3), ("abc", 2)], 5)
assert m == ["a+b 8", "ab+ab 3", "ab+c 2"] and s == ["abab", "abc"] # 第 3 轮后再无相邻对,第 4 轮提前结束五条断言覆盖两组样例、重叠计数、提前结束与「R 大于实际轮数」。完整程序(含读入与输出)在下一个展开区。
展开完整参考程序 1:AI042 BPE 子词合并训练
完整程序:AI042(标准输入 → 标准输出)
Pythonimport sys
data = sys.stdin.read().split("\n")
w, R = map(int, data[0].split())
words = [] # 每个词:(词频, 符号列表),初始每个字符一个符号
for line in data[1:1 + w]:
freq, word = line.split()
words.append((int(freq), list(word)))
merges = [] # 每轮实际执行的合并:「A+B 次数」
for _ in range(R):
count = {} # 规则 ①:加权统计相邻符号对(重叠位置各计一次,权重是词频)
for freq, syms in words:
for i in range(len(syms) - 1):
pair = (syms[i], syms[i + 1])
count[pair] = count.get(pair, 0) + freq
if not count: # 规则 ④:统计不到任何相邻对,提前结束
break
best = min(count, key=lambda p: (-count[p], p[0], p[1])) # 规则 ②:次数最高;并列先比 A 再比 B(字符串字典序)
merges.append(f"{best[0]}+{best[1]} {count[best]}")
for idx in range(w): # 规则 ③:每个词从左到右不重叠合并
freq, syms = words[idx]
out, i = [], 0
while i < len(syms):
if i + 1 < len(syms) and (syms[i], syms[i + 1]) == best:
out.append(syms[i] + syms[i + 1])
i += 2
else:
out.append(syms[i])
i += 1
words[idx] = (freq, out)
for line in merges: # 先输出每轮一行,再按输入顺序输出各词最终切分
print(line)
for _, syms in words:
print(" ".join(syms))四条规则各对应一段带编号注释的代码。用两组样例、1 1 / 5 aaa → a+a 10 / aa a(重叠计 2 次、合并不重叠)、1 3 / 4 ab → a+b 4 / ab(第 2 轮提前结束)核对。
展开完整参考程序 2:P2488 中文分词模拟器
完整程序:P2488(标准输入 → 标准输出)
Pythonimport sys
lines = sys.stdin.read().split("\n")
text = lines[0].strip()
words = set(lines[1].strip().split(",")) # 词库:集合查询
max_len = max((len(w) for w in words), default=1)
out = []
for seg in text.replace(",", " ").replace(".", " ").replace(";", " ").split(): # 标点只断句,不成词
n = len(seg)
i = 0
while i < n: # 顺序优先:从当前位置往后
j = min(n, i + max_len)
while j > i and seg[i:j] not in words: # 最长匹配:长度从大到小试
j -= 1
if j > i:
out.append(seg[i:j])
i = j
else: # 一个都不命中:单个字母成词
out.append(seg[i])
i += 1
print(",".join(out))用三组题面示例核对;思路与题目页参考题解相同(标点替换成空格再切分)。
展开完整参考程序 3:P3756 数据单元的变量替换(进阶练习)
完整程序:P3756(标准输入 → 标准输出)
Pythonimport sys
cells = sys.stdin.readline().rstrip("\n").split(",")
n = len(cells)
names = [chr(ord("A") + i) for i in range(n)]
parsed = {} # 单元格 → (引用前, 引用的单元格或 "", 引用后)
def parse(s): # 至多一对 <X>,X 必须是存在的单元格;否则返回 None
if s.count("<") == 0 and s.count(">") == 0:
return (s, "", "")
if s.count("<") != 1 or s.count(">") != 1:
return None
l, r = s.index("<"), s.index(">")
if l > r:
return None
ref = s[l + 1:r]
if ref not in names:
return None
return (s[:l], ref, s[r + 1:])
ok = True
for name, s in zip(names, cells):
p = parse(s)
if p is None:
ok = False
break
parsed[name] = p
value = {} # 已展开的单元格
state = {} # 1 = 展开中(再遇到就是环),2 = 完成
def expand(name): # 记忆化展开;自引用 / 循环引用返回 None
if state.get(name) == 2:
return value[name]
if state.get(name) == 1:
return None
state[name] = 1
before, ref, after = parsed[name]
if ref == "":
value[name] = before
else:
inner = expand(ref)
if inner is None:
return None
value[name] = before + inner + after
state[name] = 2
return value[name]
if ok:
for name in names:
if expand(name) is None:
ok = False
break
print(",".join(value[name] for name in names) if ok else -1)用第 07 节的 1,2<A>00,3<B>4 → 1,2100,321004 和五类异常各一组核对;题目页参考题解用拓扑排序判环,结果相同。
09 / 边界、反例与复杂度
错误做法在具体输入上各输出什么
下面每一行都是一个具体的错误程序,给出输入、错误输出与正确输出;把参考实现改成对应写法就能复现。
| 错误做法 | 输入 | 错误输出 | 正确输出 | 判题结果 |
|---|---|---|---|---|
| P2488 最短匹配(长度从 1 往上试) | ilovechina + 含 ilove 的词库 | i,lo,v,e,ch,i,n,a | ilove,china | 答案错误(WA) |
| P2488 标点不断句、当普通字符 | 题面示例 3 | i,love,china,,,the,…(逗号被当成单字母) | i,love,china,the,word,is,beauti,ful | 答案错误(WA) |
| P2488 未命中的字母直接丢掉 | iat | i | i,a,t | 答案错误(WA) |
| AI042 词内去重(aaa 的 (a,a) 只计 1) | aaa ×1;样例 1 | a+a 1;a+b 5 | a+a 2;a+b 8 | 答案错误(WA) |
| AI042 并列按先出现 | cd ×2、ab ×2(cd 先输入) | c+d 2 | a+b 2 | 答案错误(WA) |
| AI042 合并可重叠(合并后只跳一格) | aaa ×1 | aa aa a | aa a | 答案错误(WA) |
| AI042 提前结束后补空行 | 样例 1,R = 5 | 5 行(后两行空) | 3 行 | 答案错误(WA) |
| P3756 多重引用不报错 | <A><B>,x | 展开出一个串 | -1 | 答案错误(WA) |
第五行按「先出现」取并列:输入顺序换成 ab 在前就变成 a+b——同一组词两种答案,说明规则没按题目实现。
| 做法 | 时间 | 本课规模下 |
|---|---|---|
| P2488 逐长度尝试 + 集合查询 | O(len × 最长词长) | len < 256 瞬间完成 |
| AI042 每轮全量重算 | O(R × 总符号数) | R ≤ 50、总长 ≤ 1500,瞬间完成 |
| P3756 记忆化展开 | O(格数 + 总长) | ≤ 26 格 |
10 / 渐进练习与参考答案
跟做 → 改一个条件 → 独立实现 → 迁移
每题先在纸上或文件里做完,再展开答案。
练习 1(跟做):按第 06 节表的格式,把样例 1 的第 3 轮和第 4 轮写出来(R = 5)。
展开练习 1 答案
第 3 轮:统计只剩 (ab,c) 2 → 胜出 (ab,c) → abc ×2 合并成 abc;输出「ab+c 2」。第 4 轮:所有词都只剩一个符号,统计为空 → 提前结束。共 3 行;最终切分 abab、abc。
练习 2(改一个条件):并列规则改成「取字典序大」,样例 2 的输出变成什么?改哪一行?
展开练习 2 答案
「c+d 2」,最终 ab → a b、cd → cd。只改取最高那一行的排序键:(-count[p], p[0], p[1]) 里后两项反向(例如改用 max 并以 (count, A, B) 为键)。统计与合并不变。
练习 3(改一个条件):P2488 改成「最短匹配」,ilovechina + 含 ilove 的词库输出什么?为什么题目不采用它?
展开练习 3 答案
i,lo,v,e,ch,i,n,a(第 09 节错误表第一行):短词总是先命中,长词永远轮不到,分词毫无意义。最长匹配才让 ilove、china 这样的整词优先。
练习 4(独立实现):把 P2488 写成函数 segment(text, words),用三组题面示例和题面说明的 ilove 例子各写一条断言。
展开练习 4 答案
segment 的参考实现(自带断言)
Pythondef segment(text, words):
# text 只含小写字母与 , . ; 三种标点;words 是词库集合;顺序优先 + 最长匹配,未命中按单个字母
max_len = max((len(w) for w in words), default=1)
out = []
for seg in text.replace(",", " ").replace(".", " ").replace(";", " ").split():
n, i = len(seg), 0
while i < n:
j = min(n, i + max_len)
while j > i and seg[i:j] not in words:
j -= 1
if j > i:
out.append(seg[i:j]); i = j
else:
out.append(seg[i]); i += 1
return out
D = {"i", "love", "china", "ch", "na", "ve", "lo", "this", "is", "the", "word", "beauti", "tiful", "ful"}
assert segment("ilovechina", D) == ["i", "love", "china"] # 题面示例 1:词库里没有 ilove
assert segment("iat", D) == ["i", "a", "t"] # 示例 2:未命中按单个字母
assert segment("ilovechina,thewordisbeautiful", D) == ["i", "love", "china", "the", "word", "is", "beauti", "ful"] # 示例 3:标点只断句
assert segment("ilovechina", {"i", "ilove", "lo", "love", "ch", "china", "lovechina"}) == ["ilove", "china"] # 题面说明的例子:最长匹配长度上限用词库最长词长,避免每次都从整段长度试起。
练习 5(迁移):完成第 08 节的 bpe_train(words, rounds):两组样例、aaa 重叠、提前结束、R 大于实际轮数各写一条断言;再自拟一组「合并产生的新符号参与下一轮」的输入验证。
展开练习 5 答案
参考实现与五条断言在第 08 节展开区。自拟输入例如 abab ×3、abc ×2、R = 3:第 2 轮胜出的 (ab,ab) 本身就是第 1 轮合并出的新符号参与统计的结果;第 3 轮 (ab,c) 也是。
11 / 读题要求与复习自评
三道题的要求对照,以及完成本课之后怎么复习
提交前把下表过一遍;题目页的题面与样例是最终依据。
| 项目 | P2488 中文分词模拟器 | AI042 BPE 子词合并 | P3756 变量替换 |
|---|---|---|---|
| 输入 | 两行:文本;逗号分隔词库 | 「w R」;w 行「词频 词」 | 一行 CSV,≤ 26 格 |
| 输出 | 逗号分隔的分词 | 每轮一行「A+B 次数」,再各词切分 | 展开后的 CSV 或 −1 |
| 关键规则 | 3:断句、最长匹配、单字母 | 4:统计、取最高并列、不重叠合并、提前结束 | 解析 + 展开 + 5 类异常 |
| 示例 | ilovechina → i,love,china | 3×abab、2×abc → a+b 8 / ab+ab 3 | 本课自算 1,2<A>00,3<B>4 → 1,2100,321004 |
需要对照解法时,展开本页第 08 节的三份完整参考程序。复习与自评:本课的「学习完成检查」六条是自评,不改变题目的通过(AC)状态,本课算完成的条件以页面下方「学习完成检查」处的说明为准。复习时用三个问题自测:① 不看正文,复述 AI042 的四条规则并说出各自的最小检查用例;② 不看表格,重推样例 1 的两轮统计;③ 说出 P2488「顺序优先最长匹配」与「全局最优切分」的区别。答不出哪一条,就回到对应的节重读,再做第 10 节对应的练习。
12 / 练习
按顺序完成本课的任务
必做题已通过 0/2 道;进阶练习已通过 0/1 道
纸上算 AI042 样例 2:并列处理
代码自测自主练习练习重点:(a,b) 与 (c,d) 加权都是 2,为什么合并的是 (a,b);预计用时:10 分钟
完成标准:能说出并列规则:先比 A 的符号串,相同再比 B,按普通字符串字典序
需要时查看提示
"a" < "c",所以 (a,b) 胜出。注意比较的对象是符号串(合并后的符号可能包含多个字符),不是首字符——(ab,c) 和 (b,a) 比较时 "ab" < "b"。第 06 节有逐轮表。
P2488 · 中文分词模拟器
必做任务 1练习重点:标点断句 + 顺序优先最长匹配 + 未命中时的单字符规则;预计用时:25 分钟
完成标准:能说出「顺序优先最长匹配」和「全局最优切分」的区别
需要时查看提示
先按标点切段,段内从当前位置尝试 min(剩余长度, 词库最长词长) 到 1 的前缀,命中即前进;未命中按题目要求处理为单字符。词库用集合(set)查询,尝试用字符串切片即可,无需使用正则表达式。第 04 节有三组示例的逐段表。
AI042 · BPE 子词合并训练
必做任务 2练习重点:相邻符号对加权统计、字典序并列、从左不重叠合并、提前结束;预计用时:30 分钟
完成标准:两个样例的人工推演与程序输出完全一致;能说明 aaa 中 (a,a) 的计数方式,并写出一次不重叠合并后的结果
需要时查看提示
词的符号序列用列表(list)维护、元素为字符串,合并时从左扫:匹配到 (A,B) 就写入 AB 并跳两格,否则写入当前符号跳一格。统计和合并分两个函数,各自对照题目的第 1、3 条规则。第一行 w R,随后每行「词频 词」。第 05、06 节有规则对照与逐轮表。
P3756 · 数据单元的变量替换
进阶练习 1进阶练习练习重点:CSV 单元格引用的依赖展开;五类异常判定;预计用时:25 分钟
完成标准:能说出这题和模块 3 · 第 4 课(拓扑排序与依赖关系)的关系
需要时查看提示
单元格引用构成有向依赖,按依赖展开(记忆化递归或拓扑序);多重引用、括号不配对、引用不存在、自引用、循环引用都输出 −1。先解析每格的 (前, 引用, 后),再展开。第 07 节有逐格表与异常表。
提交结果
提交结果说明与处理方法
- WA
答案错误
四个常见错误:aaa 按词内去重只计 1 次、合并写成 aa aa a、并列按首次出现顺序而非字典序、最长匹配读成最短或全局最优。第 09 节的表给出了每种错误的具体输出
- PE
格式错误
AI042 输出两段的顺序与行数:提前结束不补空轮;P2488 分隔符逐项对照题目要求
- RE
运行错误
切片越界:尝试长度先 min(剩余长度, 最长词长);P3756 引用名不存在要先判
- TLE
超时
本课三题规模都小,超时先查是不是在循环里做了全串重建
- AC
通过
给 AI042 自行构造一组「合并产生的新符号参与下一轮」的输入,验证新符号真的进了统计
13 / 学习完成检查
本课学习完成检查
完成本课需要:必做题全部通过,并勾选本课的全部学习完成检查;进阶练习、基础加练与复习题单独统计,不影响完成状态。登录后,勾选记录会保存到账号,并更新课程总览的完成状态。