☰
蓝桥杯省赛真题解析:分糖果模拟题三种语言实现
2026/10/1 3:15:09 网站建设 项目流程

准备蓝桥杯的时候,模拟题是绕不开的一类题目。2014年第五届省赛的这道“分糖果”(题目编号1431),表面上看就是一个while循环加几次判断,但每年都有不少人在“同时分糖”和“奇数补糖”这两个细节上翻车。这篇文章把这题从头到尾拆一遍——题目规则怎么理解、解题思路怎么落地、三种语言的代码怎么写、坑在哪里,一次说清楚。不管是正在备赛C/C++组、Java组还是Python组的同学,都能直接拿来用;如果你刚开始刷算法题,想找一道简单的模拟题练手,这道也很合适。

1. 题目理解与解题思路拆解

1.1 先搞清楚游戏规则

题目本身不复杂,但规则里藏着几个容易理解错的地方。有n个小朋友围坐成一圈,老师先给每人发一些糖果,然后开始做游戏:

每一轮游戏,每个小朋友同时把自己手里糖果的一半分给坐在右边的人。分完之后,谁手里的糖果数是奇数,老师就给谁补一颗糖,让它变成偶数。重复这个过程,直到所有小朋友的糖果数一样多。题目要你输出的是:整个过程中老师一共补了多少颗糖。

乍一看这规则挺简单,但“同时”两个字非常关键。它意味着所有小朋友在给出糖果时,都是以自己这一轮开始前的糖果数为准,而不是先等左边的人给过来再决定给右边多少。我见过不少人把这道题写成了“从左到右轮流分”,结果跑出来的答案完全不对,就是因为没有理解“同时”。

另一个容易忽略的点是“围坐成一圈”。数组是线性的,但小朋友的座位是环形的,最后一个小朋友右边坐的是第一个小朋友。这个环形结构在分糖的时候必须单独处理,否则数组尾巴上那一位就会算错。

1.2 为什么说这是一道典型的模拟题

所谓模拟题,就是题目规定了完整的过程,你只需要按照规则一步步执行,不需要设计什么复杂的算法。这道“分糖果”就是最标准的模拟题:循环里做三件事——补奇数、判断相等、分糖果,循环结束条件就是所有人数相等。

但它能成为一道真题,肯定不只是考察你会写循环。真正的考点在于:你能不能处理好“同时操作”这个细节,以及能不能想清楚奇数糖果到底什么时候补。后面我会详细说这两个点。

在蓝桥杯的省赛里,模拟题占比不低,尤其是前面几道题,经常用模拟来筛掉基本功不扎实的选手。这道题看似送分,但每年考场上都有人因为细节丢分,所以把它吃透,对你做其他模拟题也有帮助。

2. 算法设计:从游戏规则到代码的关键一跃

2.1 难点一:数组里怎么模拟“同时分糖”

数组是线性的,但小朋友是围成一圈坐的,所以第n-1个小朋友右边就是第0个小朋友。这一点在分糖时要单独处理。

至于“同时”,最常见的做法是:先把每个人糖果数的一半存下来,再统一更新。但由于这道题里每个人给出的一半恰好也是别人收到的一半,所以代码上有一个更巧妙的写法,我放在第3部分代码里说。这里先理解一个原则:更新任何一个位置时,用到的其他位置数据必须是这一轮更新前的旧值。

为什么强调“旧值”?因为如果你从左往右挨个更新,a[1]在更新时,a[0]可能已经被改掉了。如果这时候还用a[0]的新值去算a[1]收到的糖,那就变成了“等待左边给完再给右边”,违背了题目“同时”的要求。

我一开始写的时候就在这上面栽过跟头。第一轮分完糖之后,数组尾部的人糖数总是莫名其妙多一颗,后来打印中间结果才定位到问题:我更新到a[n-1]时,用的a[0]已经被覆盖成新值了。这正是写模拟题最容易犯的错误——变量的更新顺序没有想清楚。

2.2 难点二:奇数糖果到底什么时候补

我最初写这题的时候,是在分完糖之后马上检查每一个人的糖果数,是奇数就补一颗。这样模拟看起来最贴近题目描述。但后来发现,这种写法在代码里会多一个“初始状态也要补一遍”的判断,而且分糖的时候还得分情况讨论除不尽的问题。

后来我换了一种思路,也推荐给你:在每一轮循环最开始,先把当前所有人的糖果数检查一遍,遇到奇数就补一颗并计数。这样做的直接好处是:进入任何一次分糖之前,所有人的糖果数都是偶数,a[i] / 2一定整除,不会出现“半颗糖”的问题。

有个细节要注意:这种“先补后分”在语义上和“分完再补”有一点差别,在中间轮的编号上会错开,但对最终补糖总数没有影响。因为每个奇数都必须被补齐之后才能参与下一轮分糖,补的总量是不变的。我自己用多组数据验证过,两种写法输出一致,但“先补后分”逻辑上更干净,新手更容易写对。

2.3 终止条件和时间复杂度

循环什么时候结束?当所有小朋友糖果数相等时。这个判断要放在补完奇数之后,因为最终状态一定是所有人糖果相等且为偶数。如果放在分糖之后判断,会导致结束条件被推迟一轮,白白浪费一次分糖操作。

复杂度方面,每轮循环要遍历数组多次,是O(n),循环轮数取决于具体数据,但n的值在竞赛数据范围内一般不会很大,所以即使跑很多轮也完全不用担心超时。这道题真正要担心的是逻辑别写错,而不是性能。

我在做这类模拟题的时候还有一个习惯:把“判断是否全部相等”单独抽成一个操作,不要和补奇数的循环混在一起。分开写虽然多一次遍历,但思路特别清晰,不容易漏判断。竞赛里多一次O(n)的遍历几乎不影响性能,但逻辑清晰能帮你少掉很多头发。

3. 代码实现:三种语言对照着写

3.1 C++实现

以下是我实际测试过的C++写法,注释里标了关键步骤:

#include <iostream> using namespace std; int main() { int n; cin >> n; int a[1005]; for (int i = 0; i < n; i++) { cin >> a[i]; } int ans = 0; // 补糖总数 while (true) { // 1. 补奇数,保证所有人糖果数为偶数 for (int i = 0; i < n; i++) { if (a[i] % 2 == 1) { a[i]++; ans++; } } // 2. 判断是否全部相等 bool flag = true; for (int i = 1; i < n; i++) { if (a[i] != a[0]) { flag = false; break; } } if (flag) { cout << ans << endl; break; } // 3. 分糖:每个人把一半给右边 int temp = a[0] / 2; // 先存下第一个人的一半,因为a[0]马上要变 for (int i = 0; i < n - 1; i++) { a[i] = a[i] / 2 + a[i + 1] / 2; } a[n - 1] = a[n - 1] / 2 + temp; } return 0; }

这段代码里最值得讲的是第3步。想象一下,分糖时每个人把手里的糖果分成两半,一半留在自己手里,一半给右边的人。所以一轮之后,第i个人的糖果数 = 自己留下的一半 + 左边的人(i-1)给自己的一半。代码里从左往右扫,a[i]更新时用的是a[i]/2和自己右边a[i+1]/2,这正好对应“自己留下的一半 + 右边的人分给自己的一半”。

最右边的a[n-1]比较特殊,它的右边是a[0],但此时a[0]已经被覆盖了,所以要在循环前用temp把a[0]原来的一半存下来。这个temp变量就是整个环形分糖的关键,忘了它,最后一个小朋友就收不到第一个小朋友的糖。

3.2 Java实现和Python实现

Java的写法几乎一模一样,只是语法不同。这里贴一个完整可运行的版本:

import java.util.Scanner; public class Main { public static void main(String[] args) { Scanner sc = new Scanner(System.in); int n = sc.nextInt(); int[] a = new int[n]; for (int i = 0; i < n; i++) { a[i] = sc.nextInt(); } int ans = 0; while (true) { for (int i = 0; i < n; i++) { if (a[i] % 2 == 1) { a[i]++; ans++; } } boolean flag = true; for (int i = 1; i < n; i++) { if (a[i] != a[0]) { flag = false; break; } } if (flag) { System.out.println(ans); return; } int temp = a[0] / 2; for (int i = 0; i < n - 1; i++) { a[i] = a[i] / 2 + a[i + 1] / 2; } a[n - 1] = a[n - 1] / 2 + temp; } } }

Python版本稍微有点不同,因为Python的整数运算不需要担心类型,但循环逻辑完全一样:

n = int(input()) a = list(map(int, input().split())) ans = 0 while True: for i in range(n): if a[i] % 2 == 1: a[i] += 1 ans += 1 if all(x == a[0] for x in a): print(ans) break temp = a[0] // 2 for i in range(n - 1): a[i] = a[i] // 2 + a[i + 1] // 2 a[n - 1] = a[n - 1] // 2 + temp

在蓝桥杯的实际判题环境中,Python版本要注意输入有可能有多余的换行或空格,用split()之后map转int是最稳妥的。如果你习惯用input().strip(),记得处理空白,否则可能因为末尾换行符导致解析出错。

还有一个小提醒:Python的all()在这里用起来很爽,但在一些判题环境中如果循环次数特别多,all()遍历也会有一点点开销。不过这道题完全不需要担心性能,怎么舒服怎么来。

4. 手算验证与边界测试

4.1 用样例数据走一遍完整过程

题目给出的样例输入是:

3 2 2 4

我们用上面的算法手算一遍:

第0轮开始前:2 2 4,没有奇数,不用补。 检查相等:2和2相等,但4不相等,继续游戏。 分糖:第0个人2个,给右边1个,自己留1个;第1个人2个,给右边1个,自己留1个;第2个人4个,给右边(第0个人)2个,自己留2个。一轮结束后变成:2 3 3。

第1轮开始:2 3 3里有奇数,3和3各补一颗,补糖数2,状态变成2 4 4。 检查相等:2和4不相等,继续。 分糖:第0个人2个,给右边1个,留1个;第1个人4个,给右边2个,留2个;第2个人4个,给右边(第0个人)2个,留2个。一轮结束后变成:3 4 3。

第2轮开始:3 4 3里有奇数,3和3各补一颗,补糖数4,状态变成4 4 4。 检查相等:全部相等,输出4。

我把这个状态变化整理成了表格:

轮次补齐后状态本轮补糖累计补糖是否相等分糖后状态
第0轮2 2 400否2 3 3
第1轮2 4 422否3 4 3
第2轮4 4 424是输出4

可以看到,这个例子的答案确实是4,和题目输出一致。我在第一次学这道题的时候,就是靠这样手算一遍才彻底搞懂了补糖和分糖的前后关系。你如果也卡在思路上,强烈建议自己拿组数据走一遍再写代码,比直接看题解管用得多。

4.2 边界情况测试

处理边界情况最能体现代码的健壮性,也是竞赛判题最容易埋坑的地方。

第一个边界是n=1。只有一个小朋友时,他的糖果数天然等于他自己,循环第一次判断就会输出0,不会进入分糖逻辑。所以上面的代码不需要为n=1单独写特殊分支。很多初学者会在这种地方过度设计,反而写出一堆if,其实没必要。

第二个边界是输入数据本身全相等,比如:

4 3 3 3 3

这种情况第一次判断就全部相等,直接输出0。注意这里有一个容易踩的坑:如果全相等且都是奇数,比如还是这组数据3 3 3 3,按照我的代码,会先补奇数,3变成4,补糖数变成4,然后判断相等,输出4。但如果你期望输出0,就会犹豫。实际上题目问的是“过程中老师补了多少颗糖”,初始状态如果包含奇数,老师也要补糖,所以输出4才是对的。这提醒我们:初始状态也要遵守“奇数必须补成偶数”的规则,因为不补成偶数就没办法进行“给出一半”这个操作。

第三个边界是轮数比较多的情况。比如:

5 1 2 3 4 5

补糖和分糖会交替进行很多轮。我实测过,最终答案是某次补糖后全部相等。这种数据用上面的循环写,跑起来没有任何压力。遇到这种数据的时候,不要试图去推公式直接算出答案,老老实实模拟反而是最快的。

5. 易错点排查与竞赛实战经验

5.1 常见错误速查表

我把看到过(包括我自己犯过的)典型错误整理成一张表,写代码的时候可以对照排查:

错误类型具体表现排查思路
分糖顺序错误从左到右轮流分,后一个小朋友的糖被前一个影响确认更新a[i]时,用到的a[i]和a[i+1]都是本轮旧值
忘记处理环形a[n-1]拿不到a[0]的一半用temp提前保存a[0]原来的一半
奇数判断时机不对分糖时出现除不尽,结果错乱确保每次分糖前把所有奇数补齐
相等判断位置不对程序多跑一轮,浪费操作;如果中间产生奇数会多补糖导致答案出错在补完奇数后、分糖前判断
数组开小了n较大时越界用1005或动态数组,别用恰好n大小的数组做循环外操作
输入解析出错读不到完整数据或读到换行符用split()后逐个转int,别直接用循环读裸数字

还有一个我自己吃了亏的点:写Java的时候,如果while(true)里最后用return而不是break,记得放在main方法里,不然会有unreachable code的报错。这种小问题很容易在考场上浪费几分钟。

5.2 我从这道题里总结的模拟题通用套路

刷多了蓝桥杯的模拟题,我发现它们都有共同点:题目描述一个过程,你只需要忠实地执行它。但执行的过程中,顺序、边界、同时性是最容易出错的三件事。

我自己的习惯是:拿到模拟题先不急着写代码,而是拿纸和笔把题目里的过程“演算”一遍,哪怕只是很小的样例。演算的过程中你会自然发现哪些地方有歧义、哪些地方需要特殊处理。比如这道题,如果不演算,很多人根本意识不到“同时分糖”其实是一个很容易写错的细节。

另外一个经验是:循环的更新顺序尽量保持一致。我这题的代码里,判断相等用的是a[0]作为基准,分糖时又是从第0个开始更新,这种统一从0开始的做法能减少很多思维负担。写模拟题的时候,变量命名和循环方向如果前后一致,调试起来会轻松很多。

最后多说一句,很多同学刷题时喜欢直接看题解,看完觉得自己会了,但一到比赛还是写不对。模拟题尤其需要自己亲手写、亲手跑、亲手改错。这道分糖果难度不大,但用来练“把文字规则翻译成代码”的能力非常合适。把这个能力练好了,后面做其他更复杂的模拟题会轻松很多。

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

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

立即咨询