洛谷 B4356 [GESP202506 二级] 数三角形
大家好!今天咱们要学习的是洛谷上的一道有趣的编程题 ——B4356 [GESP202506 二级] 数三角形。这道题是 GESP(编程能力等级认证)二级的真题,虽然看起来是几何问题,但实际上考察的是基础的循环嵌套和条件判断能力,非常适合刚接触编程的同学练习。咱们从题目本身出发,一步步理解它的求解思路~cpp运行int n,ans;n:用来存储输入的图形规模(层数);ans:用来作为计数器,统计符合
洛谷 B4356 [GESP202506 二级] 数三角形
恩师:hnjzsyjyj
一、题目介绍:认识数三角形问题
大家好!今天咱们要学习的是洛谷上的一道有趣的编程题 ——B4356 [GESP202506 二级] 数三角形。这道题是 GESP(编程能力等级认证)二级的真题,虽然看起来是几何问题,但实际上考察的是基础的循环嵌套和条件判断能力,非常适合刚接触编程的同学练习。咱们从题目本身出发,一步步理解它的求解思路~
1.1 题目来源与定位
洛谷 B4356 来自 2025 年 6 月的 GESP 二级考试,难度评级为 “入门”。这道题的核心是通过嵌套循环枚举所有可能的情况,并通过简单的数学判断统计符合条件的数量,非常适合巩固 “循环结构” 和 “条件判断” 这两个编程基础知识点。如果你已经掌握了for循环和if语句的基本用法,这道题会让你对这些知识点的实际应用有更深的理解~
1.2 题目描述
咱们先来搞清楚题目要我们做什么。题目描述可以简化为:
在一个由小三角形组成的几何图形中,我们需要统计满足特定条件的三角形数量。具体规则如下:
- 输入一个整数
n,表示图形的规模(可以理解为图形有n层); - 对于每一层
i(i从 1 到n),这一层包含i个三角形; - 每个三角形可以用一对数字
(i, j)表示,其中i是层数,j是该层中三角形的序号(j从 1 到i); - 我们需要统计所有满足 “
i × j的结果为偶数” 的三角形(i, j)的数量; - 最后输出统计的结果。
举几个例子帮助理解:
- 示例 1:当
n=1时,只有(1,1)这一个三角形。计算1×1=1,结果是奇数,所以符合条件的三角形数量是0; - 示例 2:当
n=2时,有(1,1)、(2,1)、(2,2)三个三角形:1×1=1(奇数,不计数)2×1=2(偶数,计数)2×2=4(偶数,计数)
所以结果是2;
- 示例 3:当
n=3时,共有 6 个三角形,其中符合条件的有(2,1)、(2,2)、(3,2),共3个?不对,咱们仔细算:i=1:j=1→1×1=1(奇数)→ 0i=2:j=1→2×1=2(偶),j=2→2×2=4(偶)→ 2i=3:j=1→3×1=3(奇),j=2→3×2=6(偶),j=3→3×3=9(奇)→1
总共 2+1=3,所以结果是3。
通过这些例子可以看出,问题的核心就是枚举所有i和j的组合,判断i×j是否为偶数,然后统计数量。
1.3 题目难度与适合人群
这道题属于 “入门级” 难度,适合刚学习 C++ 循环结构的同学。如果你已经掌握了for循环的嵌套使用(双层循环)和if条件判断,那么解决这道题会非常轻松。即使你是编程新手,只要跟着我一步步分析,也能很快理解并掌握解题方法~
二、解题思路:三步搞定数三角形问题
面对任何编程题,直接写代码容易出错,咱们先理清思路。解决 “数三角形” 问题可以分三步:确定枚举范围→判断条件→统计数量。咱们详细分析每一步的逻辑~
2.1 第一步:确定需要枚举的范围
题目要求统计所有(i, j)组合,其中i表示层数(从 1 到n),j表示该层中的序号(从 1 到i)。这意味着:
- 外层循环需要遍历
i,从1到n(包含n); - 对于每个
i,内层循环需要遍历j,从1到i(包含i)。
这种 “外层控制大范围,内层控制小范围” 的嵌套循环结构,是解决 “多变量组合” 问题的常用方法。比如当n=3时,外层循环i会取 1、2、3,对应内层循环j分别取 1;1、2;1、2、3,正好覆盖所有三角形的编号。
2.2 第二步:明确判断条件
对于每一对(i, j),我们需要判断 “i × j的结果是否为偶数”。数学上,两个整数相乘的结果为偶数的条件是:至少有一个数是偶数(因为偶数 × 任何数 = 偶数,奇数 × 奇数 = 奇数)。
所以判断条件可以写成:(i × j) % 2 == 0。这里的%是取余运算符,a % 2 == 0表示a是偶数。
2.3 第三步:统计符合条件的数量
我们需要一个计数器(比如变量ans),初始值为 0。每当找到一对满足条件的(i, j),就把计数器加 1。最后输出计数器的值,就是题目要求的结果。
总结一下完整的解题流程:
- 输入整数
n; - 初始化计数器
ans = 0; - 外层循环
i从 1 到n:- 内层循环
j从 1 到i:- 计算
i × j,如果结果是偶数,ans加 1;
- 计算
- 内层循环
- 循环结束后,输出
ans。
这个流程非常清晰,接下来咱们看看如何用代码实现这个过程~
三、代码解析:逐行读懂数三角形的实现
根据上面的解题思路,用户提供的代码完美实现了所有功能。咱们逐行分析这段代码:
cpp
运行
#include <bits/stdc++.h>
using namespace std;
int n,ans;
int main() {
cin>>n;
for(int i=1;i<=n;i++){
for(int j=1;j<=i;j++){
if(i*j%2==0)ans++;
}
}
cout<<ans;
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,ans;
这行代码定义了两个整数变量:
n:用来存储输入的图形规模(层数);ans:用来作为计数器,统计符合条件的三角形数量,初始值默认为 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 外层循环:遍历每一层 i
cpp
运行
for(int i=1;i<=n;i++){
// 内层循环代码
}
这是外层for循环,作用是遍历每一层i:
- 初始条件:
i=1(从第一层开始); - 循环条件:
i<=n(只要i不超过总层数n,就继续循环); - 更新操作:
i++(每次循环后,i增加 1,进入下一层)。
当n=3时,外层循环会执行 3 次,i分别为 1、2、3,正好对应图形的 3 层。
3.3.3 内层循环:遍历每层的三角形 j
cpp
运行
for(int j=1;j<=i;j++){
// 条件判断与计数代码
}
这是内层for循环,作用是遍历第i层中的每个三角形j:
- 初始条件:
j=1(从该层的第一个三角形开始); - 循环条件:
j<=i(因为第i层有i个三角形,所以j最大为i); - 更新操作:
j++(每次循环后,j增加 1,检查下一个三角形)。
当i=2时,内层循环j会取 1、2(第 2 层有 2 个三角形);当i=3时,j会取 1、2、3(第 3 层有 3 个三角形),完全符合题目中三角形的编号规则。
3.3.4 条件判断与计数
cpp
运行
if(i*j%2==0)ans++;
这行代码是整个程序的核心判断:
i*j:计算当前三角形编号(i,j)的乘积;%2==0:判断乘积是否为偶数(取余 2 等于 0 表示是偶数);ans++:如果满足条件,计数器ans增加 1。
比如当i=2、j=1时,2×1=2,2%2=0,所以ans加 1;当i=3、j=2时,3×2=6,6%2=0,ans也加 1。
3.3.5 输出结果
cpp
运行
cout<<ans;
当所有循环执行完毕后,ans中存储的就是符合条件的三角形总数,这行代码将结果输出到屏幕上。
3.3.6 程序结束
cpp
运行
return 0;
表示main函数执行完毕,程序正常退出。
四、代码执行过程演示:用实例理解循环
为了让大家更清楚代码的执行过程,咱们以n=3为例,一步步演示程序的运行:
- 初始状态:
n=3,ans=0; - 外层循环 i=1:
- 内层循环 j=1(因为 j<=1):
- 计算
1×1=1,1%2=1≠0,所以ans不变(仍为 0);
- 计算
- 内层循环结束,此时
ans=0;
- 内层循环 j=1(因为 j<=1):
- 外层循环 i=2:
- 内层循环 j=1:
- 计算
2×1=2,2%2=0,ans变为 1;
- 计算
- 内层循环 j=2:
- 计算
2×2=4,4%2=0,ans变为 2;
- 计算
- 内层循环结束,此时
ans=2;
- 内层循环 j=1:
- 外层循环 i=3:
- 内层循环 j=1:
- 计算
3×1=3,3%2=1≠0,ans不变;
- 计算
- 内层循环 j=2:
- 计算
3×2=6,6%2=0,ans变为 3;
- 计算
- 内层循环 j=3:
- 计算
3×3=9,9%2=1≠0,ans不变;
- 计算
- 内层循环结束,此时
ans=3;
- 内层循环 j=1:
- 所有循环结束,输出
ans=3,与咱们之前的手动计算结果一致。
通过这个实例可以看出,代码的执行过程完全按照咱们的解题思路进行,外层循环控制层数,内层循环控制每层的三角形,通过条件判断累计符合要求的数量。
五、易错点分析:这些坑千万别踩!
这道题虽然简单,但初学者很容易在细节上出错。咱们盘点几个常见的易错点,帮助大家避坑~
5.1 易错点 1:循环范围错误
问题描述:
内层循环的范围错误是最常见的问题,比如把j的循环条件写成j<=n(而不是j<=i),导致多统计了不属于当前层的三角形。
错误示例:
cpp
运行
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){ // 错误:j的上限应该是i,不是n
if(i*j%2==0)ans++;
}
}
当n=3时,这种写法会让每一层都统计 3 个三角形(j=1、2、3),而实际上第 1 层只有 1 个,第 2 层只有 2 个,导致结果错误(正确结果 3,错误结果会是 5)。
解决办法:
内层循环的条件必须是j<=i,确保只统计第i层实际存在的i个三角形。
5.2 易错点 2:变量初始化错误
问题描述:
如果ans变量定义在main函数内部且没有初始化,会导致初始值为随机数,最终统计结果错误。
错误示例:
cpp
运行
int main() {
int n,ans; // 错误:定义在main内的ans未初始化,初始值随机
cin>>n;
// ...循环代码...
}
假设ans的随机初始值是 5,当n=1时,正确结果是 0,但程序会输出 5。
解决办法:
- 要么将
ans定义为全局变量(如用户提供的代码),全局变量默认初始值为 0; - 要么在
main函数内定义时手动初始化:int ans = 0;。
5.3 易错点 3:判断条件错误
问题描述:
把判断条件写错,比如写成i+j%2==0(逻辑错误),或者i*j%2=0(语法错误,单等号是赋值)。
错误示例 1(逻辑错误):
cpp
运行
if(i+j%2==0)ans++; // 错误:判断的是i与(j%2)的和是否为0,不是i*j是否为偶数
比如i=3、j=2时,i+j=5是奇数,但i*j=6是偶数,这种写法会漏统计。
错误示例 2(语法错误):
cpp
运行
if(i*j%2=0)ans++; // 错误:条件判断应该用==,这里用了=会导致编译错误
解决办法:
严格使用i*j%2==0作为判断条件,注意是双等号(判断相等),且运算顺序是先乘后取余(乘法优先级高于取余)。
5.4 易错点 4:循环变量起始值错误
问题描述:
循环变量i或j从 0 开始(而不是 1),导致统计了不存在的三角形(如i=0层)。
错误示例:
cpp
运行
for(int i=0;i<n;i++){ // 错误:i从0开始,会多统计i=0的情况
for(int j=0;j<=i;j++){ // j从0开始,统计了j=0的三角形
...
}
}
题目中三角形的编号是从(1,1)开始的,i=0或j=0都是不存在的,会导致结果错误。
解决办法:
循环变量i和j的起始值必须是 1,符合题目中三角形的编号规则。
5.5 易错点 5:数据类型溢出(进阶)
问题描述:
当n很大时(比如n=10^5),i*j的结果可能超过int类型的最大值(约 21 亿),导致整数溢出,判断结果错误。
示例:
当i=10^5、j=10^5时,i*j=10^10,远大于int的最大值,溢出后结果可能变成负数,此时i*j%2的结果也会出错。
解决办法:
对于 GESP 二级的题目,n的范围通常很小(不会导致溢出),但养成良好习惯可以用long long类型避免溢出:
cpp
运行
if((long long)i*j %2 ==0)ans++; // 强制转换为long long计算
六、优化方向:让代码更高效
用户提供的代码已经能正确解题,但对于较大的n(比如n=10^4),双层循环的时间复杂度是O(n²),可能会有点慢。咱们可以通过数学分析优化算法,让代码更高效~
6.1 优化思路:通过数学公式直接计算
咱们先分析 “i×j为偶数” 的条件:两个数相乘为偶数,等价于 “至少有一个数是偶数”。反过来,“i×j为奇数” 的条件是 “两个数都是奇数”。
所以,符合条件的数量 = 总三角形数量 - 所有i和j都是奇数的三角形数量。
- 总三角形数量:1+2+3+...+n = n×(n+1)/2(等差数列求和);
- 奇数
i和奇数j的三角形数量:需要统计所有i是奇数且j是奇数(且j<=i)的情况。
假设n以内的奇数有k个(k = (n+1)//2,比如 n=5 时,奇数有 1、3、5,k=3):
- 第 1 个奇数
i=1:奇数j只能是 1 → 1 个; - 第 2 个奇数
i=3:奇数j可以是 1、3 → 2 个; - 第 3 个奇数
i=5:奇数j可以是 1、3、5 → 3 个; - ...
- 第
m个奇数i=2m-1:奇数j有m个。
所以奇数对的总数是 1+2+3+...+k = k×(k+1)/2。
因此,符合条件的数量 = [n (n+1)/2] - [k (k+1)/2],其中k=(n+1)//2。
6.2 优化代码:用数学公式计算
根据上面的分析,我们可以写出O(1)时间复杂度的代码(不需要循环):
cpp
运行
#include <bits/stdc++.h>
using namespace std;
int main() {
int n;
cin>>n;
int total = n*(n+1)/2; // 总三角形数量
int k = (n + 1) / 2; // 奇数i的个数
int odd_pairs = k*(k+1)/2; // i和j都是奇数的数量
int ans = total - odd_pairs;
cout<<ans;
return 0;
}
优点:
- 时间复杂度从
O(n²)降为O(1),即使n很大(比如n=10^6),也能瞬间计算出结果; - 代码更简洁,避免了嵌套循环的开销。
验证:
用n=3验证:
- 总数量 = 3×4/2 = 6;
k=(3+1)/2=2(奇数 i 为 1、3);- 奇数对数量 = 2×3/2=3(1+2=3);
- 结果 = 6-3=3,与之前一致。
用n=2验证:
- 总数量 = 2×3/2=3;
k=(2+1)/2=1(奇数 i 为 1);- 奇数对数量 = 1×2/2=1;
- 结果 = 3-1=2,正确。
这种优化方法体现了 “数学分析简化编程问题” 的思路,在解决计数类问题时非常有用~
七、拓展练习:巩固嵌套循环与条件判断
学会了数三角形的解法,咱们可以试试类似的题目,巩固嵌套循环和条件判断的知识点~
7.1 拓展练习 1:统计奇数乘积的三角形
题目:
输入n,统计所有(i,j)(1≤i≤n,1≤j≤i)中,i×j为奇数的三角形数量。
思路:
直接使用优化思路中的 “奇数对数量” 计算,即k×(k+1)/2,其中k=(n+1)//2。
参考代码:
cpp
运行
#include <bits/stdc++.h>
using namespace std;
int main() {
int n;
cin>>n;
int k = (n + 1) / 2;
cout<<k*(k+1)/2;
return 0;
}
7.2 拓展练习 2:统计能被 3 整除的乘积
题目:
输入n,统计所有(i,j)中,i×j能被 3 整除的三角形数量。
思路:
总数 - 不能被 3 整除的数量(i和j都不能被 3 整除)。
参考代码:
cpp
运行
#include <bits/stdc++.h>
using namespace std;
int main() {
int n;
cin>>n;
int total = n*(n+1)/2;
int cnt = 0; // 统计i和j都不能被3整除的数量
for(int i=1;i<=n;i++){
if(i%3==0)continue; // i能被3整除,跳过
for(int j=1;j<=i;j++){
if(j%3!=0)cnt++; // j也不能被3整除,计数
}
}
cout<<total - cnt;
return 0;
}
7.3 拓展练习 3:统计两数之和为偶数的情况
题目:
输入n,统计所有(i,j)中,i+j为偶数的三角形数量。
思路:
i+j为偶数的条件是i和j同奇或同偶,可分情况统计。
参考代码:
cpp
运行
#include <bits/stdc++.h>
using namespace std;
int main() {
int n,ans=0;
cin>>n;
for(int i=1;i<=n;i++){
for(int j=1;j<=i;j++){
if((i%2 + j%2) %2 ==0)ans++; // 同奇或同偶,和为偶数
}
}
cout<<ans;
return 0;
}
八、总结:数三角形背后的编程思维
通过学习洛谷 B4356 数三角形,咱们不仅掌握了这道题的解法,更理解了几个重要的编程思维:
8.1 枚举法的应用
枚举法(穷举法)是编程中解决计数类问题的基础方法:通过循环遍历所有可能的情况,再通过条件判断筛选出符合要求的对象。这道题中,我们枚举了所有(i,j)组合,正是枚举法的典型应用。
8.2 嵌套循环的逻辑
嵌套循环用于处理 “多维度” 的枚举问题:外层循环控制一个维度(这里是层数i),内层循环控制另一个维度(这里是每层的序号j)。掌握嵌套循环的逻辑,能解决很多涉及 “矩阵”“层级结构” 的问题。
8.3 数学分析简化问题
对于计数类问题,通过数学分析找到规律或公式,能大幅提高程序效率(如从O(n²)优化到O(1))。这告诉我们,编程不仅是写代码,更需要用数学思维分析问题。
8.4 细节的重要性
循环范围、变量初始化、条件判断的正确性等细节,直接决定程序是否能正确运行。编程时要养成 “关注细节、多测试边缘情况” 的习惯(如n=1、n=0等特殊值)。
九、PPT 展示建议
如果用这篇内容制作 PPT,建议按以下结构分页,突出重点,方便讲解:
- 封面页:标题 “洛谷 B4356 数三角形”+ 简单几何背景图,突出题目名称;
- 题目介绍页:用简洁语言描述题目要求,搭配 1-2 个示例(输入输出对比),用表格展示更清晰;
- 解题思路页:用流程图展示 “输入 n→嵌套循环枚举→条件判断→统计结果” 的过程,标注关键步骤;
- 代码框架页:展示完整代码,用不同颜色标出核心部分(双层循环、条件判断、计数器);
- 代码解析页:分 3 页左右,分别讲解外层循环、内层循环、条件判断的作用,结合实例(如 n=3)演示;
- 易错点页:用 “错误代码 + 正确代码” 的对比表格,列出常见错误及解决办法;
- 优化思路页:对比原始代码和优化代码,用数学公式说明优化原理,突出效率提升;
- 拓展练习页:列出 1-2 道拓展题,简要说明思路,鼓励练习;
- 总结页:用 bullet point 提炼核心知识点(枚举法、嵌套循环、数学优化),强化记忆。
这样的结构逻辑清晰,重点突出,能让初学者快速理解题目解法和背后的编程思想~
十、写在最后:编程学习的小建议
数三角形这道题虽然简单,但它涵盖了编程入门的核心技能:嵌套循环和条件判断。这些技能就像积木,看似基础,却是构建复杂程序的基础。
学习编程时,不要满足于 “能写出代码”,更要思考 “为什么这样写”“有没有更优的方法”。比如这道题,从暴力枚举到数学公式优化,就是一个思维提升的过程。
多动手敲代码,多测试不同的输入(尤其是边界值),多总结同类问题的规律,你的编程能力会在不知不觉中进步。下次遇到类似的枚举计数问题,相信你一定能举一反三,轻松解决!加油呀!
更多推荐



所有评论(0)