☰
Python复赛模拟题全解析:从质数筛法到多关键字排序
2026/10/10 6:40:45 网站建设 项目流程

每年到了这个时间点,总有学生或指导老师来问同一个问题:Python复赛到底考什么、怎么准备、题目难不难。我自己的感觉是,初赛侧重基础语法和简单逻辑,进了复赛以后,题目开始明显往上走一个台阶——它不再满足于"你会不会写这段代码",而是看你能不能把一个实际问题的约束条件、数据范围、边界情况都考虑清楚,在规定时间内写出既正确又能稳定运行的脚本。今天这篇就以一套我自己整理的Python复赛模拟题为主,把每一题的命题意图、解题思路、参考实现和踩坑点都拆开讲一遍。

这套模拟题我参考了NOC大赛创客智慧编程赛项复赛的常见命题风格,覆盖了数值计算、字符串处理、图形绘制、递推思维、数据排序这几类高频考点。不管是正在备赛的选手,还是辅导学生参赛的老师,都可以把题目当作自测材料,按比赛标准(比如限定时间、不能翻书)过一遍,再对照本文的解析查漏补缺。

1. 复赛命题规律与备考方向:先看清比赛在考什么

1.1 复赛和初赛的本质区别

初赛通常以客观题或简单的编程题为主,选手能把程序跑通、输出正确就算过关。复赛的题量通常在三到五道之间,每道题背后都有一个完整的小场景,比如处理一组学生成绩、按规则绘制某个图案、模拟一个递推过程。判分标准也更严,不只是"答案对就行",还要看运行效率、代码结构、边界情况处理。

从往年题目看,复赛高频考点大致有以下几类:

  • 数学计算与枚举:质数判断、最大公约数、水仙花数、数列求和。
  • 字符串与文本处理:字符统计、单词切分、去重排序、格式转换。
  • 图形绘制:海龟绘图(turtle),涉及循环嵌套、颜色渐变、角度计算。
  • 递推与模拟:用循环或递归描述一个逐步变化的过程,比如猴子分桃、约瑟夫问题。
  • 数据整理:字典存储、多关键字排序、按条件筛选。

1.2 高频考点分布与题型比例

我统计了一下近几年常见模拟题的内容,知识点的分布大致如下:

考点方向出现频率典型形式
数学与枚举高求区间内满足某条件的数
字符串处理高统计、反转、去重、排序
图形绘制中turtle绘制规则图形或渐变图案
递推模拟中通过若干步骤公式推导结果
数据排序中字典排序、结构性数据输出

理解这个分布的意义在于,备赛时候不必面面俱到地刷偏题怪题,把上述五大类练扎实,上场就不会慌。

1.3 我的备考资源清单建议

除了官方样题以外,我建议选手按以下路径准备:

  1. 把Python官方文档中关于列表、字典、字符串、集合的内置方法过一遍,尤其是split、join、sorted、set、defaultdict这类高频工具。
  2. 手写代码时不依赖自动补全,练到能直接在编辑器里流畅敲出完整代码。
  3. 做完每道题后主动测试边界数据,比如空列表、最大数据范围、负数输入。
  4. 学会用print()在关键位置打印中间变量,快速定位逻辑错误。

2. 上机环境与答题习惯:细节决定能不能拿满分

2.1 本地开发环境怎么搭最稳

复赛通常要求选手在指定时间内完成编程并提交结果,多数比赛提供在线代码编辑器,也可能允许使用本地IDE。我自己给学生训练时,一直推荐最朴素的组合:Python官方解释器加一个轻量编辑器,而不是一上来就用功能复杂的重型IDE。原因很简单,比赛环境中不一定有你熟悉的插件和快捷键,提前适应"裸奔"环境反而不容易慌。

关于Python版本,尽量使用3.8以上,因为新版本对类型标注、海象表达式、f-string的兼容性更好,比赛环境一般也会提供新版解释器。安装时记得勾选“Add Python to PATH”,避免在命令行里找不到python命令。如果本地安装了多个版本,在命令行用python --version确认当前解释器版本,别让代码跑在意外环境里。

2.2 拿到题目先做什么:建立输入输出模型

不管题目描述多长,第一步永远是抽象出输入输出模型。我常用的方法是画一个简单的三行草稿:

  • 输入:什么类型,一行还是多行,分隔符是什么。
  • 处理:核心逻辑涉及哪些数据结构。
  • 输出:要求的格式,是否需要保留小数,是否要按指定顺序排列。

例如一道题说“第一行输入一个正整数n,接下来n行每行有两个整数a和b,以空格分隔”,那么代码框架就清晰了:

n = int(input()) for _ in range(n): a, b = map(int, input().split()) # 处理

大多数选手失分不是因为算法不会,而是没有按这个流程做,导致读入数据时格式错位,或者输出多了空格、少了对齐。

2.3 文件命名与提交前的自查链路

有些比赛要求把代码保存为指定文件名,比如main.py。提交前必须确认文件名、文件编码(UTF-8)、入口函数名都没有问题。此外,养成一个习惯:每次修改代码后,用题目自带的样例数据跑一遍,再自己构造两到三组边界数据验证。

我见过不少学生样例测试都通过,最终分数却很惨,原因是没考虑这类情况:输入可能包含多余空格、空行、字母大小写差异、数值超出预期范围。这些在平时练习时不注意,考场上就变成致命的失分点。

3. 模拟题一:区间质数统计与筛法优化

3.1 题目描述

输入两个正整数m和n(1 <= m <= n <= 10^6),输出区间[m, n]内质数的个数,以及该区间内最大的质数。如果区间内没有质数,则输出0和-1。

这道题看似基础,但数据范围给到10^6,意味着不能对每个数都用试除法判断到根号,必须考虑效率。

3.2 解题思路分析

常规思路是遍历区间内每个数,逐个判断是否为质数。对10^6的数据范围,如果每个数都试除,总操作量约等于10^6乘以几百次,用时不稳定,在大数据量测试点下容易超时。

更合适的办法是埃拉托斯特尼筛法:先假设2到n之间的所有数都是质数,然后从2开始,把所有2的倍数标记为非质数;接着找下一个未标记的数3,把3的倍数标记;以此类推。筛法的总复杂度约为O(n log log n),对10^6这个量级完全够用。

3.3 参考实现

def prime_in_range(m, n): if n < 2: return 0, -1 is_prime = [True] * (n + 1) is_prime[0] = is_prime[1] = False for i in range(2, int(n ** 0.5) + 1): if is_prime[i]: step = i start = i * i for j in range(start, n + 1, step): is_prime[j] = False cnt = 0 max_p = -1 for num in range(m, n + 1): if is_prime[num]: cnt += 1 max_p = num return cnt, max_p m, n = map(int, input().split()) cnt, max_p = prime_in_range(m, n) print(cnt) print(max_p)

3.4 常见错误与优化点

最容易错的是边界:n < 2时直接返回;m可能等于1,但1不是质数。我在训练时反复强调,筛法初始化时一定要把0和1显式标记为False,否则这两处会让整个数组的语义错乱。

另一个细节是筛法循环里从i*i开始标记,而不是从i*2开始,因为比i*i小的合数已经被更小的质因子处理过了。这个优化虽然不至于影响正确性,但在大范围数据下能省下不少时间。实测下来,当n=10^6时,从i*i开始的版本比从i*2开始的版本快大约30%。

4. 模拟题二:字符串重排与多维字符统计

4.1 题目描述

给定一行由字母、数字和空格组成的字符串(长度不超过1000),要求:

  1. 统计其中英文字母(不区分大小写)的数量、数字字符的数量、其他字符的数量。
  2. 将所有英文字母去重后,按ASCII码升序输出,字母统一转为小写。
  3. 将数字字符按出现次数从高到低排序输出,如果出现次数相同,按数字本身从小到大排序。

输出格式要求三行分别输出上述内容。

4.2 解题拆解:不要被长描述吓到

这道题描述较长,但拆开来看就是三个独立任务。第一个任务需要遍历字符并分类;第二个任务涉及集合去重与排序;第三个任务需要用到字典统计和自定义排序规则。

这里有一个关键认知:比赛中的长题干往往在考察“信息拆分能力”,先读题,把要求逐条列出来,再动手,比边写边想更高效。

4.3 参考实现

from collections import defaultdict s = input().strip('\n') letters = [] digit_count = defaultdict(int) other_count = 0 for ch in s: if ch.isalpha(): letters.append(ch.lower()) elif ch.isdigit(): digit_count[ch] += 1 else: if ch != ' ': other_count += 1 # 任务1 print(len(letters), sum(digit_count.values()), other_count) # 任务2 unique_letters = sorted(set(letters)) print(''.join(unique_letters)) # 任务3 sorted_digits = sorted(digit_count.items(), key=lambda x: (-x[1], int(x[0]))) print(''.join(k for k, v in sorted_digits))

4.4 边界情况与易错点

处理输入时最容易出问题的地方是换行符。如果输入行末自带\n,使用input()其实已经去掉了行尾换行,但如果用sys.stdin.read()读取,就要手动处理。上面代码里strip('\n')是为了稳妥,实际上input()默认不会把换行带进来。

第三个任务里,排序规则“次数相同按数字本身升序”是通过元组(-x[1], int(x[0]))实现的。-x[1]把次数取负,次数越大排序越靠前;次数相同时,再按数字字符转成整数后升序排列。这里不转整数直接按字符串排序也能得到正确结果,因为数字字符的ASCII码顺序和数值顺序一致,但写成int(x[0])能让语义更明确,也避免以后扩展到字母时出错。

还有一个比较容易忽略的点:任务2的输出要求“按ASCII码升序”,对于小写字母来说就是a-z的字典序。如果题干里没有明确说明统一转小写,就需要自己决定处理方式。这道题显式要求了“统一转为小写”,算是降低了复杂度。

5. 模拟题三:turtle创意绘图——渐变色螺旋

5.1 题目描述

使用Python的turtle库绘制一个由多个同心正方形组成的螺旋图案,要求满足:

  • 图形由200个边长逐渐增加的正方形组成,每画完一个正方形后,画笔向右旋转一个固定角度(比如5度)。
  • 每3个正方形的填充颜色发生一次变化,颜色从深蓝渐变到浅蓝。
  • 最终图形应呈现由内向外扩展的旋转效果。

5.2 为什么这类题能进复赛

图形绘制题在复赛中属于中等难度,它不直接考算法,但考察三样东西:循环结构的组织能力、坐标系与角度变化的直觉、以及标准库模块的熟练程度。很多学生平时练turtle画线段、画圆没问题,一遇到“边画边长边旋转”的组合就开始在角度上犯迷糊。

这道题的核心在于理解:每次画完一个正方形后,画笔方向改变,下一个正方形会相对上一个旋转一定角度,边长也在增大,所以整体画面会呈现一种不断膨胀的螺旋感。如果把旋转角度设为0,那么所有正方形会重叠在一起,看不出任何效果;角度设得太大,图形又显得杂乱,5度是一个视觉上比较舒服的取值。

另外一个容易忽略的点是颜色。turtle的颜色系统有两种:默认的字符串颜色名(如"blue")和RGB颜色模式。题目要求渐变,最好使用turtle.colormode(255),然后通过整数RGB值控制颜色。

5.3 参考实现

import turtle turtle.colormode(255) t = turtle.Turtle() t.speed(0) length = 20 angle = 5 for i in range(200): if i // 3 % 2 == 0: color_val = 50 + i if color_val > 255: color_val = 255 t.color(0, 100, color_val) else: t.color(0, 0, 150) for _ in range(4): t.forward(length) t.right(90) t.right(angle) length += 3 turtle.done()

5.4 评分点细节与调试技巧

这类题通常是人工或半自动阅卷,扣分点主要在两处:一是颜色渐变是否符合“深蓝到浅蓝”的视觉预期,二是整体图形是否完整而不杂乱。

我建议学生在本地反复调整几个参数:length的增量、angle的大小、color_val的计算方式。增量太大,图案很快超出画布;增量太小,200个正方形叠在一起看不出变化。angle=5、length+=3这个组合是我在多次测试后觉得比较平衡的一组参数。

调试时还有一个很有用的技巧:临时把循环次数改小到20,先看整体形状和颜色是否符合预期,确认无误后再改回200跑完整版,这样能大幅缩短试错时间。

6. 模拟题四:递推与倒推——海滩分桃问题

6.1 题目描述

海滩上有一堆桃子,k只猴子来分。第一只猴子把桃子分成五堆,发现多了一个,于是扔掉一个,然后拿走其中一堆;第二只猴子也把剩下的桃子分成五堆,发现多了一个,同样扔掉一个后拿走一堆;以后每只猴子都如此操作。输入猴子数量k(1 <= k <= 10),求海滩上最初最少有多少个桃子。

6.2 倒推法分析:从最后反着算

这道题是经典的递推应用题。正向模拟比较难,因为你不知道最初数量;但倒推非常简单:假设最后一只猴子操作后剩下的桃子数为x,那么在它操作之前,桃子数满足:

操作前数量 = 操作后剩余量 + 1,再按比例还原。

具体来说,如果某只猴子操作后剩余量为remain,那么它操作前的数量为:

before = remain / 4 * 5 + 1

能这样倒推的前提是remain必须能被4整除,因为猴子拿走的是一堆,剩下的是四堆。于是问题变成:枚举最后剩余量,逐步向前倒推,看每一步是否能整除。

6.3 参考实现

def initial_peaches(k): x = 1 while True: remain = x valid = True for _ in range(k): # 操作前数量为 before # (before - 1) 能被 5 整除,且拿走一堆后剩下 4/5 if (remain * 4) % 5 != 0: valid = False break # 反推上一步 remain = remain * 5 // 4 + 1 if valid: return remain x += 1 k = int(input()) print(initial_peaches(k))

这段代码的核心是枚举“最后剩余量”x,然后从后往前倒推k次。每一步都要验证remain * 4能否被5整除,保证倒推过程中每一步都能还原出整数桃子数。只要某一步不满足条件,就说明当前x不是可行解,继续增大x。因为题目保证有解,最终会找到最小值。

6.4 这类题的通用解法与常见坑

递推题的解法核心是“找到相邻状态之间的数量关系”。一旦写出关系式,代码往往很短。需要注意的是,很多学生拿到题会直接正向写循环,试图通过不断增加初始数量来模拟整个过程,思路没错,但代码容易变得很啰嗦,还容易在整除判断上出错。

我见过最多的错误是倒推公式写错,写成remain = remain * 5 // 4而漏掉了+1。这个+1对应的是猴子扔掉的那个桃子,漏掉它会导致结果相差很大。另外,remain * 4和remain * 5 // 4的顺序也容易搞乱,原理是:分桃时“扔掉1个,拿走五分之一,留下五分之四”,所以剩余量remain = (before - 1) * 4 // 5,反推时先乘回5再除以4,最后加1。

建议每道递推题写完代码后,都在草稿纸上手动推演一遍小数据情况。比如k=1时,最初最少是6个桃子(扔1个、拿走1个、剩4个)。实测这个例子能快速检验公式是否写反。

7. 模拟题五:成绩数据整理与多关键字排序

7.1 题目描述

某班级进行了一次测验,输入包含学生人数n(1 <= n <= 100),随后n行每行包含三项信息:学生姓名、语文成绩、数学成绩(姓名由大小写字母组成,成绩为0到100的整数)。要求:

  1. 输出总分最高的学生姓名和总分(若有多个并列,按姓名字典序升序输出全部并列学生)。
  2. 输出数学成绩低于60分的学生名单,按语文成绩从高到低排序;语文成绩相同则按数学成绩从低到高排序。
  3. 统计并输出语文成绩的平均分(保留一位小数)。

7.2 多重排序的逻辑拆解

这道题综合考了字典、列表、排序、格式化输出。关键在于理解sorted函数的key参数可以接收一个函数,函数返回一个元组,Python会依次比较元组中的每一项。对于“按语文降序、数学升序”这种复合要求,可以写成:

key=lambda s: (-s['chinese'], s['math'])

-s['chinese']让语文成绩高的排前面;语文相同时,s['math']小的排前面。

7.3 参考实现

n = int(input()) students = [] for _ in range(n): name, chinese, math = input().split() students.append({ 'name': name, 'chinese': int(chinese), 'math': int(math) }) # 任务1 max_total = max(s['chinese'] + s['math'] for s in students) top_students = [s['name'] for s in students if s['chinese'] + s['math'] == max_total] top_students.sort() print(' '.join(top_students)) print(max_total) # 任务2 low_math = [s for s in students if s['math'] < 60] low_math.sort(key=lambda s: (-s['chinese'], s['math'])) for s in low_math: print(s['name']) # 任务3 avg_chinese = sum(s['chinese'] for s in students) / n print(f'{avg_chinese:.1f}')

7.4 输出格式陷阱与判分细节

这道题的判分点很细,稍有偏差就会丢分。首先是并列情况的处理,任务1要求“输出全部并列学生”,缺失任何一个都会判错。其次是排序规则,任务2要求“语文成绩相同则按数学成绩从低到高”,这个顺序一旦写反,样例也许能过,但隐藏测试点必然暴露问题。

此外,输出格式里姓名之间用空格分隔,没有说明是否允许末尾空格,稳妥做法是不输出多余空格,把姓名收集成列表后统一join。平均分保留一位小数,用f'{avg_chinese:.1f}'可以保证四舍五入。如果题目要求的是截断而非四舍五入,那就要用int(avg_chinese * 10) / 10做处理,这一点需要仔细读题。

8. 上机前的时间分配与我的实测心得

8.1 每道题的时间规划建议

以一套5道复赛模拟题、考试时长120分钟为例,我的建议是这样分配:

  • 前10分钟:通读全部题目,在草稿纸上标注每道题的输入、输出、核心逻辑。
  • 第10至70分钟:按“先易后难”的顺序完成自己能快速拿分的题。一般字符串和排序题比较容易,递推题次之,图形题由于要调参,放在稍后做。
  • 第70至100分钟:回头处理第一轮跳过或没做完整的题目,优先保证拿过程分(比如写清楚思路、输出部分结果)。
  • 最后20分钟:统一检查命名、样例测试、边界数据,并清理调试代码。

8.2 一套我自己用的自查清单

每次模拟考后,我都会让学生按这个清单检查代码:

  • 输入读入是否有可能错位:比如行数、空格、空行、类型转换失败。
  • 输出格式是否严格匹配:换行位置、小数点位数、分隔符、是否有多余空格。
  • 极端数据是否能跑:空字符串、最大范围数值、并列情况。
  • 是否残留调试输出:比如多余的print()、未注释的观察语句。
  • 文件名是否按题目要求命名,文件编码是否为UTF-8。

8.3 备赛阶段的一些个人体会

我带过的学生里,进步最快的一类人不是刷题最多的,而是每次做完题都认真复盘“为什么这里会错”的人。有一位学生A每次模拟考后会把错题按类型整理成一个表格,把题目、错误原因、正确思路、类似题都列出来,考前只看这张表就够了。这个习惯坚持了一个月,他的复赛成绩比第一次模拟考提升了将近一半。

另外,我建议每隔几天就完整地做一次“无参考模拟”:不开任何资料、不看题解、严格计时。这样能让自己适应比赛节奏,也能暴露平时练习时不容易发现的习惯问题——比如写代码时过度依赖自动补全、遇到问题第一反应是翻笔记而不是自己推演。考场上没有这些依赖,提前适应非常重要。

最后分享一个小技巧:拿到试卷后,不管题目看起来多熟悉,都先在草稿纸上把输入输出例子手动推演一遍。这个过程既能验证你对题意的理解,又能在写代码前预判可能的边界情况,值得每个人养成习惯。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询