PYTHON LESSON 126
真题精讲:射箭计分与最短路计数
吃透两道八级真题风格编程题:距离比较用平方避免浮点误差,复合题拆成“模板 + 小扩展”。
00 · 学习目标
这一课要解决什么?
学完后,你应该能够
- d2 = (x - a) ** 2 + (y - b) ** 2 与 r * r 比较
- d2 == r2 判压线、d2 < r2 判圆内
- dist 与 cnt 双数组同步推进
01 · 核心概念
几何判定的平方比较法 + 最短路计数复合题
几何判定的平方比较法 + 最短路计数复合题:圆心 (a, b)、半径 r,每支箭一个落点:圆内 10 分、压线 5 分、圆外 0 分。剥掉射箭的故事外壳,本质就是“点与圆的位置关系”判定——读题时把故事翻译成数学对象,是真题第一道关卡。
GESP Python 8 级 · 代数与平面几何计算GESP Python 8 级 · 单源最短路(Dijkstra)有向无环图、拓扑排序、强连通分量和负权最短路是图论后续主题。
02 · 语法与规则
先记住这 3 条,再开始写程序
d2 = (x - a) ** 2 + (y - b) ** 2 与 r * r 比较先准确读出这条写法的结构与作用。
d2 == r2 判压线、d2 < r2 判圆内换一组最小数据,手工推演一次结果。
dist 与 cnt 双数组同步推进再用边界值或反例确认它的适用条件。
03 · 完整实例
代码、运行结果和解释放在一起看
# 真题精讲:两道八级真题风格编程题一次讲透
# —— 题 1:射箭计分(几何判定,对应题库 py-exam-l8-a1)——
def archery(a, b, r, points):
total = 0
r2 = r * r
for x, y in points:
d2 = (x - a) ** 2 + (y - b) ** 2 # 关键技巧:比较距离的平方,不开根号
if d2 < r2:
total += 10 # 圆内(不含边界)10 分
elif d2 == r2:
total += 5 # 恰好压线 5 分
return total
# 样例:圆心 (0, 0),半径 5,共 5 支箭
pts = [(3, 4), (5, 0), (6, 0), (0, 0), (-3, -4)]
print("射箭总得分:", archery(0, 0, 5, pts)) # 5+5+0+10+5
# —— 题 2:最短上学路线有几条(Dijkstra + 计数,对应题库 py-exam-l8-a2)——
import heapq
def count_shortest(n, edges, start, end):
MOD = 10**9 + 7
graph = [[] for _ in range(n + 1)]
for u, v, w in edges:
graph[u].append((v, w))
graph[v].append((u, w))
INF = float("inf")
dist = [INF] * (n + 1)
cnt = [0] * (n + 1)
dist[start] = 0
cnt[start] = 1
heap = [(0, start)]
while heap:
d, u = heapq.heappop(heap)
if d > dist[u]:
continue
for v, w in graph[u]:
nd = d + w
if nd < dist[v]: # 更短:距离与方案数一起更新
dist[v] = nd
cnt[v] = cnt[u]
heapq.heappush(heap, (nd, v))
elif nd == dist[v]: # 一样短:方案数累加并取模
cnt[v] = (cnt[v] + cnt[u]) % MOD
return dist[end], cnt[end]
# 样例:4 个路口、4 条双向路,1→2→4 与 1→3→4 并列最短
edges = [(1, 2, 2), (1, 3, 2), (2, 4, 3), (3, 4, 3)]
d, c = count_shortest(4, edges, 1, 4)
print("最短路程:", d, "路线条数:", c)射箭总得分: 25 最短路程: 5 路线条数: 2
题 1 五支箭分别得 5、5、0、10、5 分;题 2 的 1→2→4 与 1→3→4 并列最短(长度都是 5),所以是 2 条。
04 · 逐步理解
每一步只解决一个问题
- 01
题 1 读题:翻译成几何判定
圆心 (a, b)、半径 r,每支箭一个落点:圆内 10 分、压线 5 分、圆外 0 分。剥掉射箭的故事外壳,本质就是“点与圆的位置关系”判定——读题时把故事翻译成数学对象,是真题第一道关卡。
# 位置关系:d 与 r 比大小 # d < r 圆内 / d = r 压线 / d > r 圆外 - 02
题 1 关键技巧:比平方,不开方
算真实距离要开根号,浮点误差随之而来——而压线判断偏偏是“相等”比较,最怕误差。技巧:全程比较距离的平方 d2 与 r²,整数运算零误差,还省了一次 sqrt。几何题看到距离比较,先想平方。
d2 = (x - a) ** 2 + (y - b) ** 2 if d2 < r2: ... elif d2 == r2: ... - 03
题 1 边界:< 与 == 别写反
“圆内(不含边界)”用严格小于,“恰好压线”用等于——两个分支写反,样例可能照样过,大数据直接翻车。考试把“含不含边界”圈出来,再写分支。
- 04
题 2 读题:复合题 = 模板 + 扩展
“最短路程 + 最短路线有几条”就是 122 课的复合题型:Dijkstra 骨架一行不动,只多带一个 cnt 数组。真题的“难题”很多是老模板穿新衣服,认出骨架就赢了一半。
dist[start] = 0 cnt[start] = 1 - 05
题 2 编码与数据范围
更短就 dist、cnt 一起更新,等长就 cnt 累加取模。注意范围:10^5 个点、2×10^5 条边,邻接表加堆优化是必须,邻接矩阵光内存就要 10^10 个格子,直接爆。读题先估算,老规矩。
elif nd == dist[v]: cnt[v] = (cnt[v] + cnt[u]) % MOD - 06
真题四步套路总结
一建模:把故事翻译成图或几何对象;二套模板:Dijkstra、平方比较这些老朋友直接写;三查边界:压线、起点、取模、编号从 0 还是从 1;四对样例:手算一遍再对程序输出。四步走完,提交才有底气。
05 · 练习与检验
自己写出来,才算真正学会
- 1运行实验室,核对得分 25、路线 2 条
- 2手算 (3, 4) 到圆心的距离平方是 25
- 3把半径改成 3,预测得分怎么变
- 4总结“模板 + 扩展”的复合题套路
06 · 完整知识
继续理解定义、规则和适用边界
第一次学习先完成上面的六个步骤;需要查定义、核对规则、分析误区或理解“为什么”时,再展开对应知识章。
核心数据结构树、图、DFS、BFS 与最短路从节点与边的模型出发,掌握遍历、连通性、树结构、最小生成树和最短路径的适用条件。+
正式定义
图由顶点和边构成;树是连通且无环的无向图。DFS 沿路径深入后回溯,BFS 按距离层次扩展。算法的正确选择取决于图是否有向、边权是否为负、是否稠密以及目标是遍历、连通还是最短路。
必须掌握
- 邻接矩阵占 O(V²) 空间,适合稠密图和快速查边;邻接表占 O(V+E),适合稀疏图。
- DFS 常用递归或显式栈,BFS 使用队列;一般图都必须记录 visited 防止重复和死循环。
- 无权图的 BFS 首次到达即得到最少边数距离,可用 parent 还原路径。
- 树有 V-1 条边且任意两点路径唯一;二叉树前/中/后序描述根的访问时机。
- Dijkstra 只适用于非负边权;Floyd 求所有点对最短路并允许负边,但不能有可达负环。
- Kruskal 按边权排序并用并查集避环,得到连通无向带权图的最小生成树。
常见误区
- 遍历一般图时不记录 visited
- 对负权边使用 Dijkstra
- 混淆最短路径树与最小生成树
- 递归 DFS 忽略深度限制
适用边界
- 有向无环图、拓扑排序、强连通分量和负权最短路是图论后续主题。
- 网格搜索也是图搜索:格子是顶点,可移动关系是边;不要只背二维数组模板。
Python 基础运算符、表达式与优先级完整区分算术、比较、逻辑、成员、身份和位运算,并用优先级表消除歧义。+
正式定义
表达式求值得到一个值。运算符规定如何组合操作数;当一个表达式含多个运算符时,优先级和结合方向决定求值顺序,括号可以明确改变顺序。
必须掌握
- / 总是得到浮点结果;// 是向负无穷方向取整的整除;% 与 // 满足 a == (a // b) * b + a % b。
- 比较可以链式书写,如 0 <= x < 10;and/or 会短路并返回最后求值的操作数,不一定返回 bool。
- == 比较值是否相等,is 比较是否为同一个对象;判断 None 应写 is None。
- in/not in 做成员测试;对 dict 测试的是键。
- 位运算作用于整数的二进制位;负整数按无限长二进制补码语义理解。
- 复杂表达式即使能靠优先级正确运行,也应使用括号表达意图。
常见误区
- 把 // 当成简单截断
- 用 is 比较数字或字符串的值
- 忘记 and 的优先级高于 or
- 连续位移、比较和逻辑运算却不加括号
适用边界
- 浮点数比较受二进制表示误差影响,需要按问题选择容差。
- 运算符可由自定义类重载,因此相同符号对不同类型可能有不同语义。
数学基础计数原理、组合数与计算几何基础明确有序/无序、互斥/分步与几何退化边界,再把公式转为稳定程序。+
正式定义
加法原理用于互斥方案分类求和,乘法原理用于依次完成步骤求积;排列关心顺序,组合不关心顺序。计算几何用坐标、向量、距离和方向关系把图形条件转成数值判定。
必须掌握
- A(n,m)=n!/(n-m)!,C(n,m)=n!/(m!(n-m)!),并有 C(n,m)=C(n,n-m)。
- 杨辉三角递推 C(n,m)=C(n-1,m-1)+C(n-1,m),边界 C(n,0)=C(n,n)=1。
- 取模下的组合数算法取决于 n、模数是否为质数、查询次数和是否允许预处理。
- 距离比较应优先比较平方,避免不必要的 sqrt 和浮点误差。
- 三角形必须满足任意两边之和大于第三边;共线、重合、零长度是几何边界。
- 浮点比较应按题目误差使用容差;整数坐标能用叉积时优先保持整数计算。
常见误区
- 未判断顺序是否重要就套排列组合公式
- 模意义下直接做普通除法
- 浮点数直接用 ==
- 漏掉共线或退化图形
适用边界
- 概率需要在计数结果上再建立样本空间,不等同于组合数本身。
- 复杂计算几何涉及方向、相交、凸包等更多主题,本章只覆盖课程实际使用的基础判定。
算法方法算法、复杂度与解题验证把题意转成输入、状态、规则与输出,用正确性和复杂度共同评价解法。+
正式定义
算法是解决一类问题的有限、明确步骤。正确性说明算法对所有满足前置条件的输入都得到规定结果;时间和空间复杂度描述输入规模增长时资源使用的增长量级。
必须掌握
- 先明确输入规模 n、数据范围、目标和允许误差,再选择数据结构与算法。
- O(1)、O(log n)、O(n)、O(n log n)、O(n²)、O(2ⁿ) 表示增长量级,不是精确运行秒数。
- 顺序代码复杂度取较大项,嵌套循环常相乘,二分每步把范围缩小一半。
- 正确性可用循环不变量、数学归纳、交换论证、反证或状态定义来说明。
- 样例只验证少量输入;必须自己设计边界、极端、重复、有序/逆序和无解数据。
- 优化前先得到正确基线并测量瓶颈,不为小数据盲目增加复杂实现。
常见误区
- 只看样例通过就宣称正确
- 不看数据范围使用 O(n²)
- 二分区间开闭混用
- 把 O(n) 当成永远比 O(log n) 慢固定倍数
适用边界
- 复杂度隐藏常数与硬件差异,但仍是比较规模增长的核心工具。
- 考场策略、课程完成度和算法能力是不同证据,任何单项都不能保证考级通过。
完成检查