洛谷 B4356 [GESP202506 二级] 数三角形

恩师:hnjzsyjyj

一、题目介绍:认识数三角形问题

大家好!今天咱们要学习的是洛谷上的一道有趣的编程题 ——B4356 [GESP202506 二级] 数三角形。这道题是 GESP(编程能力等级认证)二级的真题,虽然看起来是几何问题,但实际上考察的是基础的循环嵌套和条件判断能力,非常适合刚接触编程的同学练习。咱们从题目本身出发,一步步理解它的求解思路~

1.1 题目来源与定位

洛谷 B4356 来自 2025 年 6 月的 GESP 二级考试,难度评级为 “入门”。这道题的核心是通过嵌套循环枚举所有可能的情况,并通过简单的数学判断统计符合条件的数量,非常适合巩固 “循环结构” 和 “条件判断” 这两个编程基础知识点。如果你已经掌握了for循环和if语句的基本用法,这道题会让你对这些知识点的实际应用有更深的理解~

1.2 题目描述

咱们先来搞清楚题目要我们做什么。题目描述可以简化为:

在一个由小三角形组成的几何图形中,我们需要统计满足特定条件的三角形数量。具体规则如下:

  • 输入一个整数n,表示图形的规模(可以理解为图形有n层);
  • 对于每一层ii从 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=1j=1 → 1×1=1(奇数)→ 0
    • i=2j=1→2×1=2(偶),j=2→2×2=4(偶)→ 2
    • i=3j=1→3×1=3(奇),j=2→3×2=6(偶),j=3→3×3=9(奇)→1
      总共 2+1=3,所以结果是3

通过这些例子可以看出,问题的核心就是枚举所有ij的组合,判断i×j是否为偶数,然后统计数量

1.3 题目难度与适合人群

这道题属于 “入门级” 难度,适合刚学习 C++ 循环结构的同学。如果你已经掌握了for循环的嵌套使用(双层循环)和if条件判断,那么解决这道题会非常轻松。即使你是编程新手,只要跟着我一步步分析,也能很快理解并掌握解题方法~

二、解题思路:三步搞定数三角形问题

面对任何编程题,直接写代码容易出错,咱们先理清思路。解决 “数三角形” 问题可以分三步:确定枚举范围→判断条件→统计数量。咱们详细分析每一步的逻辑~

2.1 第一步:确定需要枚举的范围

题目要求统计所有(i, j)组合,其中i表示层数(从 1 到n),j表示该层中的序号(从 1 到i)。这意味着:

  • 外层循环需要遍历i,从1n(包含n);
  • 对于每个i,内层循环需要遍历j,从1i(包含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。最后输出计数器的值,就是题目要求的结果。

总结一下完整的解题流程:

  1. 输入整数n
  2. 初始化计数器ans = 0
  3. 外层循环i从 1 到n
    • 内层循环j从 1 到i
      • 计算i × j,如果结果是偶数,ans加 1;
  4. 循环结束后,输出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;:声明使用标准命名空间,这样我们可以直接使用cincout等标准库函数,不用每次都写std::cinstd::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=2j=1时,2×1=22%2=0,所以ans加 1;当i=3j=2时,3×2=66%2=0ans也加 1。

3.3.5 输出结果

cpp

运行

cout<<ans;

当所有循环执行完毕后,ans中存储的就是符合条件的三角形总数,这行代码将结果输出到屏幕上。

3.3.6 程序结束

cpp

运行

return 0;

表示main函数执行完毕,程序正常退出。

四、代码执行过程演示:用实例理解循环

为了让大家更清楚代码的执行过程,咱们以n=3为例,一步步演示程序的运行:

  1. 初始状态n=3ans=0
  2. 外层循环 i=1
    • 内层循环 j=1(因为 j<=1):
      • 计算1×1=11%2=1≠0,所以ans不变(仍为 0);
    • 内层循环结束,此时ans=0
  3. 外层循环 i=2
    • 内层循环 j=1:
      • 计算2×1=22%2=0ans变为 1;
    • 内层循环 j=2:
      • 计算2×2=44%2=0ans变为 2;
    • 内层循环结束,此时ans=2
  4. 外层循环 i=3
    • 内层循环 j=1:
      • 计算3×1=33%2=1≠0ans不变;
    • 内层循环 j=2:
      • 计算3×2=66%2=0ans变为 3;
    • 内层循环 j=3:
      • 计算3×3=99%2=1≠0ans不变;
    • 内层循环结束,此时ans=3
  5. 所有循环结束,输出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=3j=2时,i+j=5是奇数,但i*j=6是偶数,这种写法会漏统计。

错误示例 2(语法错误):

cpp

运行

if(i*j%2=0)ans++; // 错误:条件判断应该用==,这里用了=会导致编译错误
解决办法:

严格使用i*j%2==0作为判断条件,注意是双等号(判断相等),且运算顺序是先乘后取余(乘法优先级高于取余)。

5.4 易错点 4:循环变量起始值错误

问题描述:

循环变量ij从 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=0j=0都是不存在的,会导致结果错误。

解决办法:

循环变量ij的起始值必须是 1,符合题目中三角形的编号规则。

5.5 易错点 5:数据类型溢出(进阶)

问题描述:

n很大时(比如n=10^5),i*j的结果可能超过int类型的最大值(约 21 亿),导致整数溢出,判断结果错误。

示例:

i=10^5j=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为奇数” 的条件是 “两个数都是奇数”。

所以,符合条件的数量 = 总三角形数量 - 所有ij都是奇数的三角形数量

  • 总三角形数量: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:奇数jm个。

所以奇数对的总数是 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≤n1≤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 整除的数量(ij都不能被 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为偶数的条件是ij同奇或同偶,可分情况统计。

参考代码:

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=1n=0等特殊值)。

九、PPT 展示建议

如果用这篇内容制作 PPT,建议按以下结构分页,突出重点,方便讲解:

  1. 封面页:标题 “洛谷 B4356 数三角形”+ 简单几何背景图,突出题目名称;
  2. 题目介绍页:用简洁语言描述题目要求,搭配 1-2 个示例(输入输出对比),用表格展示更清晰;
  3. 解题思路页:用流程图展示 “输入 n→嵌套循环枚举→条件判断→统计结果” 的过程,标注关键步骤;
  4. 代码框架页:展示完整代码,用不同颜色标出核心部分(双层循环、条件判断、计数器);
  5. 代码解析页:分 3 页左右,分别讲解外层循环、内层循环、条件判断的作用,结合实例(如 n=3)演示;
  6. 易错点页:用 “错误代码 + 正确代码” 的对比表格,列出常见错误及解决办法;
  7. 优化思路页:对比原始代码和优化代码,用数学公式说明优化原理,突出效率提升;
  8. 拓展练习页:列出 1-2 道拓展题,简要说明思路,鼓励练习;
  9. 总结页:用 bullet point 提炼核心知识点(枚举法、嵌套循环、数学优化),强化记忆。

这样的结构逻辑清晰,重点突出,能让初学者快速理解题目解法和背后的编程思想~

十、写在最后:编程学习的小建议

数三角形这道题虽然简单,但它涵盖了编程入门的核心技能:嵌套循环和条件判断。这些技能就像积木,看似基础,却是构建复杂程序的基础。

学习编程时,不要满足于 “能写出代码”,更要思考 “为什么这样写”“有没有更优的方法”。比如这道题,从暴力枚举到数学公式优化,就是一个思维提升的过程。

多动手敲代码,多测试不同的输入(尤其是边界值),多总结同类问题的规律,你的编程能力会在不知不觉中进步。下次遇到类似的枚举计数问题,相信你一定能举一反三,轻松解决!加油呀!

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐