洛谷 B4358 [GESP202506 三级] 奇偶校验
大家好呀!今天咱们要一起学习洛谷上的一道经典编程题 ——B4358 [GESP202506 三级] 奇偶校验。这道题虽然难度不大,但却是理解 “二进制运算” 和 “循环统计” 的绝佳案例,非常适合刚接触编程不久的同学巩固基础知识。咱们从题目本身出发,一步步揭开它的面纱~cppint n:用来存储输入的整数个数;int a[105]:定义了一个大小为 105 的整数数组,用来存储输入的n个整数(10
洛谷 B4358 [GESP202506 三级] 奇偶校验
点点关注 是我创作的最大动力
恩师:hnjzsyjyj
一、题目介绍:初识奇偶校验问题
大家好呀!今天咱们要一起学习洛谷上的一道经典编程题 ——B4358 [GESP202506 三级] 奇偶校验。这道题虽然难度不大,但却是理解 “二进制运算” 和 “循环统计” 的绝佳案例,非常适合刚接触编程不久的同学巩固基础知识。咱们从题目本身出发,一步步揭开它的面纱~
1.1 题目来源与定位
洛谷 B4358 来自 2025 年 6 月的 GESP 三级考试真题,难度评级为 “入门” 到 “普及” 之间。它主要考察对二进制数的理解、循环结构的灵活运用以及简单的统计逻辑,是将数学概念与编程实践结合的典型题目。如果你已经掌握了for循环、while循环和基本的算术运算,那么解决这道题会非常轻松~
1.2 题目描述
咱们先来搞清楚题目要我们做什么。题目描述可以简化为:
输入一个整数n,表示接下来有n个整数;然后输入这n个整数,我们需要完成以下操作:
- 统计这
n个整数的二进制表示中,所有 “1” 的总个数(记为ans); - 计算
ans除以 2 的余数(即ans % 2); - 最后输出
ans和ans % 2这两个结果,中间用空格隔开。
举几个例子帮助理解:
- 示例 1:输入
2 3 5。3 的二进制是11(含 2 个 1),5 的二进制是101(含 2 个 1),总共有2+2=4个 1,4%2=0,所以输出4 0; - 示例 2:输入
3 1 2 4。1 的二进制是1(1 个 1),2 是10(1 个 1),4 是100(1 个 1),总共有1+1+1=3个 1,3%2=1,所以输出3 1; - 示例 3:输入
1 0。0 的二进制是0(0 个 1),所以输出0 0。
通过这些例子可以看出,问题的核心就是将每个整数转换为二进制后统计 “1” 的总数,再计算总数的奇偶性。
1.3 题目难度与适合人群
这道题属于 “入门到普及过渡” 的难度,适合已经掌握 C++ 基本循环结构和算术运算的同学练习。如果你理解二进制的概念,会用while循环处理重复操作,并且知道取余运算(%)的用法,那么这道题对你来说会很轻松。即使你对二进制不太熟悉,跟着我一步步分析,也能很快掌握解题方法~
二、解题思路:三步搞定奇偶校验问题
面对编程题,直接写代码容易出错,咱们先理清思路。解决 “奇偶校验” 问题可以分三步:读取输入数据→统计每个数的二进制中 1 的个数→计算结果并输出。咱们详细分析每一步的逻辑~
2.1 第一步:读取输入数据
题目要求先输入n,再输入n个整数。这意味着:
- 首先用
cin读取n的值; - 然后用一个
for循环,循环n次,每次读取一个整数,存储到数组中(方便后续处理)。
数组就像一个 “储物柜”,把这n个整数按顺序存起来,需要的时候再一个个取出来处理。这里我们可以定义一个大小合适的数组(比如a[105]),足够存储题目中的输入数据。
2.2 第二步:统计每个数的二进制中 1 的个数
这是题目最核心的步骤。对于每个整数a[i],我们需要统计它的二进制表示中 “1” 的数量。怎么统计呢?
二进制数的特点是 “逢二进一”,每个数位只有 0 或 1。我们可以用 “除以 2 取余” 的方法逐步拆解二进制数:
- 每次将整数除以 2,得到的余数要么是 0,要么是 1(这个余数就是当前二进制的最后一位);
- 如果余数是 1,就把计数器
ans加 1; - 然后将整数更新为 “除以 2 的商”,重复上述过程,直到整数变成 0 为止。
举个例子:统计5的二进制中 1 的个数:
- 5 ÷ 2 = 2,余数 1(是 1,ans+1,此时 ans=1);
- 2 ÷ 2 = 1,余数 0(不是 1,ans 不变);
- 1 ÷ 2 = 0,余数 1(是 1,ans+1,此时 ans=2);
- 整数变成 0,停止循环,所以 5 的二进制中有 2 个 1。
这种 “循环取余” 的方法,能精准地提取二进制数中的每一位,从而统计 1 的个数。
2.3 第三步:计算结果并输出
当所有数的 1 的个数都统计完毕后,我们需要:
- 计算
ans(总个数); - 计算
ans % 2(总个数的奇偶性,0 表示偶数,1 表示奇数); - 用
cout输出这两个结果,中间用空格隔开。
总结一下完整的解题流程:
- 输入
n和n个整数,存入数组a; - 初始化计数器
ans = 0; - 对数组中的每个数
a[i]:- 用
while循环拆解二进制:- 计算
a[i] % 2,如果结果是 1,ans加 1; - 将
a[i]更新为a[i] / 2; - 重复直到
a[i]变成 0;
- 计算
- 用
- 计算
ans % 2,输出ans和这个结果。
三、代码解析:逐行读懂奇偶校验的实现
根据上面的解题思路,用户提供的代码完美实现了所有功能。咱们逐行分析这段代码:
cpp
#include<bits/stdc++.h>
using namespace std;
int n,a[105],ans;
int main() {
cin>>n;
for(int i=1;i<=n;i++){
cin>>a[i];
while(a[i]>0){
if(a[i]%2==1)ans++;
a[i]/=2;
}
}
cout<<ans<<" "<<ans%2;
return 0;
}
这段代码虽然简短,但逻辑清晰,咱们一步步拆解:
3.1 头文件与命名空间:代码的 “基础配置”
cpp
#include<bits/stdc++.h>
using namespace std;
这两行是 C++ 代码的标准开头,作用是:
#include<bits/stdc++.h>:引入 C++ 的万能头文件,包含了所有常用的标准库(如输入输出、数组处理等),省去了逐个引入头文件的麻烦;using namespace std;:声明使用标准命名空间,这样我们可以直接使用cin、cout等标准库函数,不用每次都写std::cin、std::cout。
对于入门级题目,这两行代码几乎是标配,大家可以直接记住~
3.2 变量定义:存储数据的 “容器”
cpp
int n,a[105],ans;
这行代码定义了三个变量,分别是:
int n:用来存储输入的整数个数;int a[105]:定义了一个大小为 105 的整数数组,用来存储输入的n个整数(105 的大小足够应对题目中的数据范围);int ans:用来作为计数器,统计所有二进制中 1 的总个数,初始值默认为 0(全局变量未初始化时默认值为 0)。
这里将变量定义在main函数外面(全局变量),所以ans初始值为 0。如果定义在main函数内部,需要手动初始化ans = 0,否则可能会有随机值,导致结果错误~
3.3 主函数:程序的 “核心逻辑”
main函数是程序的入口,所有操作都在这里执行。咱们分步骤解析:
3.3.1 读取整数个数 n
cpp
cin>>n;
这行代码从输入中读取n的值,n就是接下来要处理的整数的数量。比如输入3,表示后面有 3 个整数需要处理。
3.3.2 循环读取 n 个整数并处理
cpp
for(int i=1;i<=n;i++){
cin>>a[i];
while(a[i]>0){
if(a[i]%2==1)ans++;
a[i]/=2;
}
}
这部分是整个程序的核心,咱们分两层分析:
外层 for 循环:读取并处理每个整数
- 循环条件:
i从 1 到n(因为数组下标从 1 开始更符合咱们的计数习惯); - 每次循环先执行
cin>>a[i]:读取一个整数,存储到数组的第i个位置; - 然后执行内层的
while循环,处理这个整数,统计它的二进制中 1 的个数。
内层 while 循环:统计二进制中 1 的个数
- 循环条件:
a[i]>0(只要当前整数大于 0,就继续拆解); if(a[i]%2==1)ans++:计算当前整数除以 2 的余数,如果余数是 1(说明二进制最后一位是 1),就把计数器ans加 1;a[i]/=2:将当前整数除以 2(相当于去掉二进制的最后一位),继续下一次循环。
举个例子,当a[i] = 5时(二进制是 101):
- 第一次循环:
5%2=1(余数是 1),ans加 1(ans=1);5/2=2(此时 a [i] 变为 2); - 第二次循环:
2%2=0(余数是 0),ans不变;2/2=1(a [i] 变为 1); - 第三次循环:
1%2=1(余数是 1),ans加 1(ans=2);1/2=0(a [i] 变为 0); - 此时
a[i] = 0,while循环结束,5 的二进制中共有 2 个 1,统计完成。
这个过程就像 “剥洋葱”,一层层剥开二进制数的每一位,遇到 1 就计数,非常直观~
3.3.3 输出结果
cpp
cout<<ans<<" "<<ans%2;
当所有整数都处理完毕后,ans中存储的是所有二进制中 1 的总个数。这行代码输出两个结果:
- 第一个是
ans(总个数); - 第二个是
ans%2(总个数除以 2 的余数,即奇偶性); - 两个结果之间用空格隔开,符合题目要求的输出格式。
3.3.4 程序结束
cpp
return 0;
表示main函数执行完毕,程序正常退出。
四、代码执行过程演示:用实例理解逻辑
为了让大家更清楚代码的执行过程,咱们以示例 1(输入2 3 5)为例,一步步演示程序的运行:
- 初始状态:
n未赋值,a数组为空,ans=0; - 读取 n:
cin>>n后,n=2; - 外层循环 i=1:
- 读取
a[1]:cin>>a[1]后,a[1]=3; - 内层
while(a[1]>0)循环(处理 3):- 第一次循环:
3%2=1→ans=1;3/2=1→a[1]=1; - 第二次循环:
1%2=1→ans=2;1/2=0→a[1]=0; - 循环结束(
a[1]=0);
- 第一次循环:
- 读取
- 外层循环 i=2:
- 读取
a[2]:cin>>a[2]后,a[2]=5; - 内层
while(a[2]>0)循环(处理 5):- 第一次循环:
5%2=1→ans=3;5/2=2→a[2]=2; - 第二次循环:
2%2=0→ans不变;2/2=1→a[2]=1; - 第三次循环:
1%2=1→ans=4;1/2=0→a[2]=0; - 循环结束(
a[2]=0);
- 第一次循环:
- 读取
- 所有循环结束:输出
ans和ans%2,即4 0,与示例结果一致。
通过这个实例可以看出,代码的执行过程完全按照咱们的解题思路进行,每一步都清晰可控~
五、易错点分析:这些坑千万别踩!
这道题虽然逻辑不复杂,但初学者很容易在细节上出错。咱们盘点几个常见的易错点,帮助大家避坑~
5.1 易错点 1:数组越界或大小不足
问题描述:
如果数组定义得太小(比如a[10]),而输入的n大于数组大小(比如n=15),就会导致数组越界,程序可能崩溃或输出错误结果。
错误示例:
cpp
int a[10]; // 错误:数组大小太小
cin>>n; // 假设n=15
for(int i=1;i<=n;i++){
cin>>a[i]; // 当i=10时,a[10]超出数组范围
}
解决办法:
定义数组时预留足够的空间。题目中n的范围通常不会太大(GESP 三级题一般n≤100),定义a[105]或a[1005]完全足够,避免数组越界。
5.2 易错点 2:计数器 ans 未初始化
问题描述:
如果ans定义在main函数内部且没有初始化,初始值会是随机数,导致统计结果错误。
错误示例:
cpp
int main() {
int n,a[105],ans; // 错误:ans未初始化,初始值随机
cin>>n;
// ...循环代码...
}
假设ans的随机初始值是 5,当输入n=1 0时,正确结果是0 0,但程序会输出5 1。
解决办法:
- 要么将
ans定义为全局变量(如用户提供的代码),全局变量默认初始值为 0; - 要么在
main函数内定义时手动初始化:int ans = 0;。
5.3 易错点 3:处理 0 的情况
问题描述:
0 的二进制表示是0,其中没有 1,但有些同学可能会误以为 0 有 1 个 1,或者在while循环中漏处理 0 的情况。
错误示例:
cpp
while(a[i]>=0){ // 错误:循环条件写成>=0,0会进入循环
if(a[i]%2==1)ans++;
a[i]/=2;
}
当a[i]=0时,0%2=0,ans不变,但0/2=0,会导致while循环无限执行(死循环)。
解决办法:
while循环的条件必须是a[i]>0,确保 0 不会进入循环(因为 0 的二进制中没有 1,无需统计)。当a[i]=0时,while循环不执行,ans保持不变,符合预期。
5.4 易错点 4:整数除以 2 的方式错误
问题描述:
在更新a[i]时,误写成a[i] = a[i] / 2但忘记整数除法的特性,或者写成a[i] %= 2(取余而不是除法)。
错误示例:
cpp
a[i]%=2; // 错误:这是取余操作,不是除法
比如a[i]=3时,3%2=1,之后a[i]一直是 1,导致循环无限执行。
解决办法:
正确使用除法更新a[i]:a[i] /= 2(等价于a[i] = a[i] / 2),确保每次循环都能去掉二进制的最后一位,逐步将a[i]减小到 0。
5.5 易错点 5:输出格式错误
问题描述:
题目要求输出两个结果,中间用空格隔开,但有的同学可能会忘记加空格,或者多输出一个空格,导致格式错误。
错误示例 1(没加空格):
cpp
运行
cout<<ans<<ans%2; // 输出结果连在一起,比如“40”
错误示例 2(多一个空格):
cpp
运行
cout<<ans<<" "<<ans%2<<" "; // 最后多一个空格,比如“4 0 ”
解决办法:
严格按照题目要求输出,两个结果之间用一个空格隔开,不要多也不要少。正确写法是cout<<ans<<" "<<ans%2;,简洁规范。
5.6 易错点 6:循环变量范围错误
问题描述:
外层for循环的范围错误,比如写成i=0开始或i<n结束,导致漏处理或多处理数据。
错误示例:
cpp
运行
for(int i=0;i<n;i++){ // 错误:i从0开始,但输入到a[0]
cin>>a[i];
// ...处理代码...
}
如果n=2,会读取a[0]和a[1],虽然也能处理,但与代码中数组下标从 1 开始的习惯不符,容易混淆。更严重的是如果循环条件写成i<=n,会多循环一次,读取不存在的数据。
解决办法:
外层循环严格控制范围,i从 1 开始,到n结束(i<=n),确保正好读取n个整数,与题目要求一致。
六、优化方向:让代码更高效简洁
用户提供的代码已经能正确解题,但我们还可以从几个角度优化,让代码更高效、更易读~
6.1 优化 1:不用数组,直接处理数据
题目中并不需要保存所有整数,我们可以边读取边处理,省去数组的使用,节省内存空间。
优化代码:
cpp
运行
#include<bits/stdc++.h>
using namespace std;
int n,x,ans;
int main() {
cin>>n;
for(int i=1;i<=n;i++){
cin>>x; // 直接用变量x存储当前整数,不存数组
while(x>0){
if(x%2==1)ans++;
x/=2;
}
}
cout<<ans<<" "<<ans%2;
return 0;
}
优点:
- 省去数组的定义和存储,代码更简洁;
- 减少内存占用,尤其在
n较大时更明显。
6.2 优化 2:使用位运算统计 1 的个数
在计算机中,二进制数的操作可以用位运算更高效地实现。x & 1可以直接获取x二进制的最后一位(等价于x%2),x >>= 1可以实现x = x / 2的效果,且位运算速度更快。
优化代码:
cpp
运行
#include<bits/stdc++.h>
using namespace std;
int n,x,ans;
int main() {
cin>>n;
for(int i=1;i<=n;i++){
cin>>x;
while(x>0){
if(x & 1)ans++; // x&1等价于x%2==1
x >>= 1; // x>>=1等价于x /= 2
}
}
cout<<ans<<" "<<ans%2;
return 0;
}
优点:
- 位运算的执行效率比算术运算更高,尤其在处理大量数据时更明显;
- 代码更贴近计算机底层逻辑,适合理解二进制操作。
6.3 优化 3:提前处理负数(拓展)
题目中输入的是整数,但如果输入负数(虽然题目可能不包含负数,但作为拓展),负数的二进制表示有符号位,需要特殊处理。在 C++ 中,负数采用补码表示,符号位为 1,会导致统计错误。
处理负数的优化代码:
cpp
运行
#include<bits/stdc++.h>
using namespace std;
int n,x,ans;
int main() {
cin>>n;
for(int i=1;i<=n;i++){
cin>>x;
unsigned int num = x; // 用无符号整数处理负数,忽略符号位
while(num>0){
if(num & 1)ans++;
num >>= 1;
}
}
cout<<ans<<" "<<ans%2;
return 0;
}
优点:
- 用
unsigned int存储数据,将负数视为无符号数处理,避免符号位的干扰; - 拓展了代码的适用范围,能正确处理负数的二进制统计。
七、拓展练习:巩固二进制统计能力
学会了奇偶校验的解法,咱们可以试试类似的题目,巩固二进制统计和循环处理的知识点~
7.1 拓展练习 1:统计每个数的二进制 1 的个数
题目:
输入n和n个整数,对每个整数,输出它的二进制表示中 1 的个数,每个结果占一行。
思路:
对每个数单独统计 1 的个数,统计完后立即输出,不需要累计总和。
参考代码:
cpp
运行
#include<bits/stdc++.h>
using namespace std;
int main() {
int n,x;
cin>>n;
while(n--){
cin>>x;
int cnt=0;
while(x>0){
if(x%2==1)cnt++;
x/=2;
}
cout<<cnt<<endl;
}
return 0;
}
7.2 拓展练习 2:计算二进制中 1 的个数的最大值
题目:
输入n和n个整数,找出其中二进制表示中 1 的个数最多的数,输出这个最大值。如果有多个数的 1 的个数相同且最多,输出其中最大的数。
思路:
- 对每个数统计 1 的个数,记录最大个数和对应的数;
- 如果遇到个数更多的数,更新最大值和对应数;
- 如果个数相同,比较数的大小,保留较大的数。
参考代码:
cpp
运行
#include<bits/stdc++.h>
using namespace std;
int main() {
int n,x;
cin>>n;
int max_cnt=0, result=0;
while(n--){
cin>>x;
int cnt=0, temp=x;
while(temp>0){
if(temp%2==1)cnt++;
temp/=2;
}
if(cnt>max_cnt || (cnt==max_cnt && x>result)){
max_cnt=cnt;
result=x;
}
}
cout<<result;
return 0;
}
7.3 拓展练习 3:二进制中 1 的位置
题目:
输入一个整数x,输出它的二进制表示中所有 1 所在的位置(从右往左数,最右边为第 1 位)。如果x=0,输出-1。
思路:
- 用循环拆解二进制,记录每个 1 出现的位置(循环次数 + 1 就是位置);
- 存储位置信息,最后按顺序输出。
参考代码:
cpp
运行
#include<bits/stdc++.h>
using namespace std;
int main() {
int x;
cin>>x;
if(x==0){
cout<<-1;
return 0;
}
vector<int> pos; // 存储1的位置
int idx=1; // 位置从1开始
while(x>0){
if(x%2==1)pos.push_back(idx);
x/=2;
idx++;
}
for(int i=0;i<pos.size();i++){
cout<<pos[i]<<" ";
}
return 0;
}
八、总结:奇偶校验背后的编程思维
通过学习洛谷 B4358 奇偶校验,咱们不仅学会了这道题的解法,更掌握了几个重要的编程思维和知识点:
8.1 二进制的理解与操作
二进制是计算机的基础,“除以 2 取余” 是将十进制转换为二进制的经典方法。通过这道题,咱们学会了如何用编程实现二进制的拆解和统计,理解了二进制数的存储逻辑。
8.2 循环的嵌套使用
外层for循环控制数据的输入和整体流程,内层while循环处理单个数据的二进制拆解,这种 “外层控制数量,内层处理细节” 的嵌套循环结构,是处理批量数据的常用技巧。
8.3 计数器的使用
计数器ans从 0 开始,通过条件判断累计符合要求的数量,最终得到结果。这种 “初始化→循环累计→输出结果” 的计数模式,在统计类问题中非常常见。
8.4 细节处理的重要性
数组大小的选择、计数器的初始化、循环条件的设置、输出格式的规范,这些细节直接影响程序的正确性。编程时要养成 “关注细节、多测试边缘情况” 的习惯(如输入 0、输入较大的数)。
九、PPT 展示建议
如果用这篇内容制作 PPT,建议按以下结构分页,突出重点,方便讲解:
- 封面页:标题 “洛谷 B4358 奇偶校验”+ 二进制相关背景图(如 0 和 1 组成的图案),突出题目名称;
- 题目介绍页:用简洁语言描述题目要求,搭配 1-2 个示例(输入输出对比),用表格展示更清晰;
- 解题思路页:用流程图展示 “输入数据→二进制拆解→统计 1 的个数→计算结果” 的过程,标注关键步骤;
- 二进制原理页:简单介绍二进制的基本概念,用 “除以 2 取余” 的例子(如 5→101)帮助理解;
- 代码框架页:展示完整代码,用不同颜色标出核心部分(外层循环、内层循环、计数器);
- 代码解析页:分 2-3 页,分别讲解外层
for循环、内层while循环的作用,结合实例(如 n=2,3 和 5)演示执行过程; - 易错点页:用 “错误代码 + 正确代码” 的对比表格,列出常见错误及解决办法,重点标注数组越界、计数器初始化等问题;
- 优化方向页:展示优化后的代码(如位运算版本),对比讲解优化思路和优点;
- 拓展练习页:列出 1-2 道拓展题,简要说明思路,鼓励练习巩固;
- 总结页:用 bullet point 提炼核心知识点(二进制操作、循环嵌套、计数器使用),强化记忆。
这样的结构逻辑清晰,重点突出,能让初学者快速理解题目解法和背后的编程思想,非常适合课堂讲解或自我复习~
十、写在最后:从奇偶校验到更多二进制问题
奇偶校验虽然是一道简单的题目,但它涉及的二进制操作和循环统计逻辑,是很多复杂问题的基础。比如计算机网络中的数据校验、数字电路中的逻辑设计、密码学中的加密算法,都离不开二进制的处理。
学习编程时,不要满足于 “写出能运行的代码”,更要理解代码背后的原理:为什么用while(x>0)循环?为什么x%2能得到二进制的最后一位?这些原理的理解,能帮助你解决更复杂的问题。
多动手敲代码,多测试不同的输入(比如 0、负数、较大的数),多思考 “有没有更优的解法”,你的编程能力会在不知不觉中提升。下次遇到涉及二进制的问题,相信你一定能举一反三,轻松解决!加油呀!
更多推荐



所有评论(0)