资讯动态

NOIP经典算法题解析:乒乓球比赛规则模拟与字符串处理实战

发布时间:2026/8/28 7:03:38 来源:尧图企业网站定制
1. 项目概述从一道经典算法题看信息学竞赛的思维训练“乒乓球”这个标题乍一看可能让人联想到运动但在信息学竞赛的语境下它指的是一道来自2003年全国青少年信息学奥林匹克联赛NOIP普及组的经典题目。这道题远不止是模拟一场乒乓球比赛那么简单它是一道精巧的“字符串处理”与“规则模拟”的综合应用题是无数OIer信息学奥林匹克竞赛选手入门时绕不开的“思维体操”。我当年第一次接触这道题时也被它看似简单实则暗藏玄机的描述给“坑”过后来在带学生备赛时更是发现它几乎涵盖了新手从理解题意到实现代码的全过程痛点。这道题的核心是要求我们根据给定的比赛比分记录由‘W’和‘L’字符组成的字符串分别代表华华和对手得一分按照乒乓球比赛的两种赛制11分制和21分制分别输出比赛结果。题目输入就是一串字符输出则是两列格式规整的比分。它考察的不仅仅是编程语言的基本功更是对问题抽象、规则理解、边界条件处理以及输出格式控制等综合能力的检验。对于刚接触竞赛编程的新手来说这是一道绝佳的练手题对于有经验的开发者回顾这道题也能重新审视我们处理“规则驱动型”数据流问题的基本方法论。2. 题目核心需求与规则深度解析2.1 赛制规则的形式化定义要正确解决这个问题首先必须抛开对乒乓球运动的常识依赖严格形式化题目中给出的比赛规则。题目规则可以提炼为以下几点一局比赛当一方得分达到11分或21分且领先对方至少2分时这一局结束。一场比赛由多局组成直到所有输入记录处理完毕。输入记录一个由‘W’华华得分和‘L’对手得分组成的字符串可能包含‘E’字符表示记录结束。‘E’不一定出现在字符串末尾也可能在中间一旦遇到‘E’后续记录不再处理。输出要求分别按照11分制和21分制输出每局比赛的比分。每局比分格式为“华华得分:对手得分”每局比分单独一行。最后一行需要输出当前这局未完成比赛的比分如果遇到‘E’终止。这里最容易产生误解的是“且领先至少2分”这个条件。很多人会写成“得分11 得分-对手得分2”这个逻辑在大部分情况下是对的但忽略了一种特殊情况当比分从10:1021分制下是20:20开始交替上升时比赛并不会在11:10或21:20时结束因为领先优势只有1分。比赛会一直持续到某一方领先2分比如12:10, 13:11, 22:20, 24:22等。因此结束条件是一个循环判断而非一次性的条件判断。2.2 输入数据的处理与边界条件输入是一个字符串我们需要顺序处理每一个字符。这里有几个关键的边界情况需要考虑‘E’的位置‘E’可能出现在字符串的任何位置。处理逻辑必须是顺序扫描一旦遇到‘E’立即停止处理后续字符。最终的输出必须包含遇到‘E’时正在进行的那一局的当前比分。空输入或仅含‘E’的输入理论上如果输入只有一个‘E’那么根据规则应该输出两场空比赛的结果吗题目通常隐含输入至少包含一个有效字符W或L但严谨的程序应该能处理这种情况输出“0:0”或直接无输出。在实际竞赛中通常会保证测试数据中有有效记录但养成处理边界情况的习惯至关重要。字符串长度原始题目未明确给出字符串长度上限但在实际编程中我们需要考虑存储问题。在C/C中可能用字符数组在Python/Java中可以用字符串类型直接读取。关键在于我们的算法应该是在线算法或离线算法都能实现。在线算法即边读入边处理节省内存离线算法即先读入整个字符串再处理。对于此题两种皆可但在线处理更显功底。注意一个常见的“坑”是选手容易先读取完整字符串再根据‘E’的位置进行切片处理。这固然可以但必须注意如果‘E’之后还有字符在切片后就不应再被处理。更稳健的方法是在读取或遍历过程中一旦遇到‘E’就跳出循环。3. 算法设计与实现思路拆解3.1 模拟法的核心流程这道题最直观的解法就是模拟法。我们设定两个变量score_a和score_b分别记录华华和对手在当前局的得分。然后遍历输入字符串的每一个字符如果字符是‘W’则score_a。如果字符是‘L’则score_b。如果字符是‘E’则立即终止遍历。在每次更新分数后判断当前局是否结束检查是否有一方得分达到赛制要求11或21分以上。如果是则进一步检查得分差是否大于等于2。如果同时满足以上两个条件则当前局结束。将本局比分score_a:score_b输出然后将score_a和score_b重置为0开始新的一局。遍历结束后输出最后一局可能未完成的比分score_a:score_b。这个流程需要运行两次一次用于11分制一次用于21分制。我们可以分别写两个函数或者用一个函数接受“赛制分数”作为参数。3.2 代码结构规划一个清晰的结构有助于减少错误。建议规划如下主函数负责读取输入数据。由于输入可能有多行在OJ系统中可能直到文件结束EOF我们需要持续读入并拼接字符串直到遇到‘E’字符或读入结束。更简单的方法是题目通常保证输入只有一行我们可以直接读取一整行。核心模拟函数simulate(game_str, rule_score)参数比赛记录字符串game_str赛制分数rule_score(11或21)。初始化当前局比分a 0, b 0和一个用于存储所有局比分的列表results。遍历game_str更新比分。判断局点if (a rule_score or b rule_score) and abs(a - b) 2:。若到达局点则将(a, b)加入results并重置a, b为0。若遇到‘E’立即跳出循环。遍历结束后将未完成的最后一局比分(a, b)也加入results。返回results列表。输出函数将simulate函数返回的比分列表按行输出为“a:b”的格式。3.3 关键逻辑的代码实现示例Pythondef simulate(records, win_score): 模拟乒乓球比赛 :param records: 比赛记录字符串包含W,L,E :param win_score: 赛制分数11或21 :return: 包含每局比分的列表每个元素为 (华华得分, 对手得分) a b 0 # 当前局比分 games [] # 存储所有已结束的局 for ch in records: if ch E: break # 遇到E立即终止 elif ch W: a 1 elif ch L: b 1 # 判断当前局是否结束 if (a win_score or b win_score) and abs(a - b) 2: games.append((a, b)) a b 0 # 重置开始新的一局 # 循环结束后添加未完成的最后一局如果有比分的话 # 注意这里即使a和b都是0也添加进去。但输出时如果整个比赛没有有效记录results会是[(0,0)]。 # 更严谨的做法是如果a0 and b0 and not games则不添加。但题目通常要求输出。 games.append((a, b)) return games def main(): import sys # 读取所有输入直到遇到E。注意输入可能跨多行。 input_str [] for line in sys.stdin: input_str.append(line.strip()) if E in line: # 找到E的位置只取到E之前的部分进行拼接包含E # 更简单的方法是先拼接然后在simulate函数里遇到E跳出 break records .join(input_str) # 11分制 games_11 simulate(records, 11) for ga, gb in games_11: print(f{ga}:{gb}) print() # 输出空行分隔题目通常要求 # 21分制 games_21 simulate(records, 21) for ga, gb in games_21: print(f{ga}:{gb}) if __name__ __main__: main()这段代码清晰地体现了模拟思想。simulate函数是核心它严格遵循了“处理记录 - 更新比分 - 判断局点 - 存储重置”的流程。注意我们在函数内部判断局点并在循环结束后追加未完成局的比分。4. 常见陷阱与调试技巧实录4.1 新手常犯的错误局点判断逻辑错误错误1if a win_score or b win_score:。这忽略了领先2分的要求会导致在10:10时下一分到11:10就错误结束比赛。错误2if (a win_score or b win_score) and (a - b 2 or b - a 2):。这个逻辑是对的但用abs(a-b) 2更简洁。错误3在更新比分前判断局点。顺序必须是先加分再判断。因为局点是在得到这一分之后才可能产生的。‘E’字符处理不当忘记在遇到‘E’时立即break导致继续处理了‘E’之后的无效字符。在读取输入时没有正确处理‘E’可能出现在一行中间的情况。使用上述先拼接再统一处理的方法并在模拟函数中break可以避免这个问题。输出格式问题忘记在两种赛制结果之间输出一个空行。这是OJ在线评测系统常见的格式要求少了空行会导致“Presentation Error”输出格式错误。最后一局未完成比赛的比分忘记输出。模拟循环结束后a和b变量里存储的正是最后一局的比分必须输出。初始化与重置错误在每一局结束后忘记将a和b重置为0导致比分累加到下一局。在开始模拟21分制前忘记重新初始化变量直接使用了模拟11分制后残留的变量值。必须为两种赛制独立运行模拟过程。4.2 调试与测试用例设计要验证程序正确性需要设计覆盖各种边界情况的测试用例测试用例描述输入记录11分制预期输出21分制预期输出考察点普通对局WWWWWWWWWWW11:011:0一方直接获胜恰好净胜2分WLWLWLWLWLWW(假设打到11:9)11:911:9达到11分且领先2分平分后决胜WL重复多次形成10:10后WW12:1012:10超过11分才结束中途结束WWLLWE2:2(注遇到E时比分2:2)2:2‘E’字符处理只有EE0:0(或无数出)0:0空输入边界长局测试大量WL交替模拟21分制下20:20后WLW根据11分制会输出多局22:2021分制规则和高分差局实操心得在本地调试时不要只看简单用例。一定要模拟“拉锯战”比如手动构造一个长达上百字符的WLWLWL...序列让程序跑一遍检查在11分制和21分制下分局是否正确。特别是从一局过渡到下一局时比分重置是否干净。4.3 性能与优化思考对于这道题字符串长度一般不会极大模拟法的时间复杂度是O(n)完全足够。但我们可以思考一些优化和变种在线处理如果输入数据量巨大比如来自一个流我们不应该存储整个字符串。可以逐字符读取并处理遇到‘E’或EOF停止。这样内存消耗是O(1)。函数复用如示例代码所示写一个通用的simulate函数通过参数区分赛制避免代码重复。输出优化在有些语言中频繁的print调用可能较慢。可以先将所有结果组装到一个字符串或列表中最后一次性输出有时能提升效率。5. 从解题到思维拓展规则模拟类问题的通用解法“乒乓球”题是一个典型的规则模拟问题。解决这类问题可以总结出一个通用的四步法精确定义规则用数学或逻辑语言严格、无歧义地定义所有规则。像本题中的“至少领先2分”必须转化为abs(a-b) 2这样的可执行逻辑。设计状态变量找出模拟过程中需要跟踪的所有状态。本题中就是当前局的a分、b分以及已结束的局列表games。构建事件循环确定状态如何随着输入事件本题中的每个字符更新。画出状态转移图在脑中会非常清晰W - aL - b 然后判断是否触发“局结束”事件若触发则保存状态并重置。处理初始与终止状态初始化所有状态变量。明确循环终止条件遇到‘E’或字符串末尾并处理好终止后的遗留状态输出未完成的最后一局。将这道题掌握后可以尝试解决更复杂的模拟题例如模拟棋类游戏、电梯调度、银行排队等。其内核都是状态机思想系统在几个离散状态中切换事件驱动状态转移并在特定条件下产生输出。我个人在教授这道题时发现很多学生卡住不是因为不会写if-else而是没能把模糊的自然语言规则转化成精确的计算机逻辑。这道“乒乓球”题就像一把钥匙帮他们打开了“计算思维”这扇门——编程不只是写代码更是对现实世界规则进行严密定义和建模的过程。当你能够清晰地将“赢下一局”的条件用两行代码表达出来时你就已经迈出了从普通使用者到创造者的关键一步。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价