GESP2026年9月认证C++五级( 第三部分编程题(1、哥德巴赫猜想))精讲



一、先把题目变成一个"小侦探故事" 🔍

假设老师给小明一个数字:

复制代码
10

问:

10 可以由多少种不同的"两个素数之和"组成?

我们知道:

复制代码
10 = 3 + 7
10 = 5 + 5

所以答案是:

复制代码
2

而且:

复制代码
3 + 7
7 + 3

是按照同一种方法。

题目明确规定:

只有两个分解方案中的素数集合不同,才算不同方案。

所以我们不能把:

复制代码
3 + 7
7 + 3

算成两次。


二、这道题真正问的是什么?

我们可以把问题浓缩成一句话:

给你一个偶数 n,找出所有满足 p + q = n 的素数对 (p,q),而且每一对只能计算一次。

例如:

n = 10

尝试:

复制代码
2 + 8
3 + 7
4 + 6
5 + 5

其中:

  • 2 是素数,但 8 不是 ❌

  • 3 是素数,7 也是素数 ✅

  • 4 不是素数 ❌

  • 5 是素数,5 也是素数 ✅

所以:

复制代码
10 = 3 + 7
10 = 5 + 5

答案:

复制代码
2

三、第一关:什么是素数?⭐

一个大于 1 的整数:

只能被 1 和它自己整除

就是素数。

例如:

复制代码
2   √
3   √
4   ×
5   √
6   ×
7   √
8   ×
9   ×
10  ×
11  √

所以:

复制代码
2 3 5 7 11

都是素数。


四、如果一个一个判断素数,可以吗?

当然可以。

比如我们枚举:

复制代码
2
3
4
5
...
n/2

然后判断:

复制代码
i 是不是素数?
n-i 是不是素数?

如果两个都是素数:

复制代码
ans++;

就可以了。

但是问题来了:

如果 n 很大呢?

如果每次都重新判断一个数是不是素数,就可能重复做大量工作。

所以这道题的漂亮解法是:

⭐ 先把所有素数一次性找出来!

这就是:

埃氏筛------筛素数


五、埃氏筛:像筛面粉一样筛掉合数

我们准备一个数组:

复制代码
bool not_prime[1000005];

它的含义是:

复制代码
not_prime[x] == true

表示:

x 不是素数。

反过来:

复制代码
not_prime[x] == false

表示:

x 目前还是"素数候选人"。

可以把它想象成:

🌟 一开始所有数字都参加"素数选拔赛",然后我们不断把合数淘汰掉。


六、筛素数第一步:1 不是素数

程序:

复制代码
not_prime[1] = true;

因为:

复制代码
1

不是素数。


七、从 2 开始检查

参考程序:

复制代码
for (int i = 2; i <= n; ++i) {
    if (!not_prime[i]) {
        primes[pcnt++] = i;

        for (int j = 2; i * j <= n; ++j) {
            not_prime[i * j] = true;
        }
    }
}

我们一步一步看。


八、遇到 2:发现 2 是素数

因为:

复制代码
not_prime[2] == false

所以:

复制代码
2 是素数

把它保存:

复制代码
primes[pcnt++] = 2;

然后把 2 的倍数全部标记成合数:

复制代码
2×2 = 4
2×3 = 6
2×4 = 8
2×5 = 10
...

于是:

复制代码
4 ×
6 ×
8 ×
10 ×
...

都被淘汰。


九、遇到 3

3 还没有被标记:

复制代码
not_prime[3] == false

所以:

复制代码
3 是素数

保存:

复制代码
primes = {2,3}

然后把:

复制代码
3×2 = 6
3×3 = 9
3×4 = 12
...

标记成合数。


十、遇到 4

这时候:

复制代码
not_prime[4] == true

说明:

复制代码
4 已经被 2 淘汰

所以:

复制代码
if (!not_prime[i])

不成立。

直接跳过。


十一、最后得到一个"素数通讯录"

比如:

复制代码
n = 20

最后:

复制代码
primes =

2
3
5
7
11
13
17
19

这就是我们的素数名单。

参考程序就是先通过 get_primes() 完成这件事情。


十二、第二关:找到两个素数

现在假设:

复制代码
n = 20

我们已经知道:

复制代码
2 3 5 7 11 13 17 19

都是素数。

那么我们要寻找:

复制代码
p + q = 20

例如:

复制代码
2 + 18   ❌
3 + 17   ✅
5 + 15   ❌
7 + 13   ✅
11 + 9   ❌

所以:

复制代码
20 = 3 + 17
20 = 7 + 13

答案:

复制代码
2

十三、为什么只枚举到 n/2?

这是这道题最重要的一个小技巧。

参考程序:

复制代码
primes[i] <= n / 2

为什么?

假设:

复制代码
n = 20

如果我们枚举到:

复制代码
11

那么:

复制代码
20 - 11 = 9

这已经超过一半了。

而前面其实已经检查过:

复制代码
20 = 9 + 11

所以再检查:

复制代码
11 + 9

就是重复计算。


十四、这就是"去重"的秘密 ⭐⭐⭐

假设:

复制代码
n = 10

如果我们全部枚举:

复制代码
2 + 8
3 + 7
4 + 6
5 + 5
6 + 4
7 + 3
8 + 2

你会发现:

复制代码
3 + 7
7 + 3

重复了。

所以我们只检查:

复制代码
p <= n/2

也就是:

复制代码
p <= 5

只需要:

复制代码
2 + 8
3 + 7
4 + 6
5 + 5

这样:

复制代码
3 + 7

出现一次。

复制代码
7 + 3

根本不会再出现。

这就是一种非常重要的:

"只枚举一半,自动避免重复"


十五、程序中这一句非常关键

参考程序:

复制代码
for (int i = 0; i < pcnt && primes[i] <= n / 2; ++i)

可以拆成:

复制代码
i < pcnt

表示:

还没有走完素数数组。

以及:

复制代码
primes[i] <= n / 2

表示:

只检查前一半的素数。


十六、然后检查另一个数字是不是素数

程序:

复制代码
if (!not_prime[n - primes[i]])
    ans++;

这句话看起来有点吓人,我们翻译成"小学生语言":

假设:

复制代码
n = 20
primes[i] = 7

那么另一个数字就是:

复制代码
20 - 7 = 13

程序检查:

复制代码
!not_prime[13]

因为:

复制代码
13 是素数

所以:

复制代码
not_prime[13] = false

那么:

复制代码
!false = true

于是:

复制代码
ans++;

答案加 1。


十七、完整走一遍 n = 20

我们来做一张"侦探表"。

第一个素数 第二个数 20-p 是素数吗? 算不算
2 18 ❌ 不算
3 17 ✅ ✅
5 15 ❌ 不算
7 13 ✅ ✅
11 9 ❌ 不算

所以:

复制代码
20 = 3 + 17
20 = 7 + 13

答案:

复制代码
2

十八、再看一个 n = 28

素数:

复制代码
2 3 5 7 11 13 17 19 23

只检查:

复制代码
p <= 14

于是:

复制代码
2 + 26 ❌
3 + 25 ❌
5 + 23 ✅
7 + 21 ❌
11 + 17 ✅
13 + 15 ❌

所以:

复制代码
28 = 5 + 23
28 = 11 + 17

答案:

复制代码
2

十九、现在来看完整参考程序

参考程序核心结构是:先筛素数,再枚举不超过 n/2 的素数,并检查 n-primes[i] 是否也是素数。

复制代码
#include <cassert>
#include <cstdio>
using namespace std;

int n, ans;

bool not_prime[1000005];

int primes[500000], pcnt = 0;

void get_primes() {
    not_prime[1] = true;

    for (int i = 2; i <= n; ++i) {
        if (!not_prime[i]) {
            primes[pcnt++] = i;

            for (int j = 2; i * j <= n; ++j) {
                not_prime[i * j] = true;
            }
        }
    }
}

int main() {
    scanf("%d", &n);

    get_primes();

    for (int i = 0;
         i < pcnt && primes[i] <= n / 2;
         ++i) {

        if (!not_prime[n - primes[i]])
            ans++;
    }

    printf("%d\n", ans);

    return 0;
}

二十、把程序分成"三个房间" 🏠

其实程序只有三个任务。

🏠 房间1:输入

复制代码
scanf("%d", &n);

得到:

复制代码
n

🏠 房间2:制作素数名单

复制代码
get_primes();

完成:

复制代码
2
3
5
7
11
13
...

🏠 房间3:寻找答案

复制代码
for (...)
    if (...)
        ans++;

也就是:

找到一个素数 p,再看看 n-p 是不是素数。


二十一、同学们一定要理解的核心思想

这道题千万不要只记代码。

应该记住下面这个"魔法公式":

⭐ 核心公式

如果:

复制代码
n = p + q

那么:

复制代码
q = n - p

所以:

枚举一个素数 p,只需要检查 n-p 是不是素数。

而为了避免:

复制代码
p + q
q + p

重复:

只枚举 p <= n/2。


二十二、为什么这道题需要"筛素数"?

假如我们要检查:

复制代码
n - p

是不是素数。

如果每次都从 2 开始试除:

复制代码
2
3
4
5
...

会比较慢。

于是我们提前做一次:

复制代码
埃氏筛

把:

复制代码
2~n

里面所有素数找出来。

以后判断:

复制代码
not_prime[x]

就可以:

复制代码
O(1)

知道它是不是素数。

这就是算法思想中的:

"先预处理,再快速查询"


二十三、最容易犯的 4 个错误 ⚠️

错误1:把 p+q 和 q+p 算两次

例如:

复制代码
3+7
7+3

不能算两次。

解决办法:

复制代码
primes[i] <= n / 2

只枚举一半。


错误2:忘记 1 不是素数

程序:

复制代码
not_prime[1] = true;

就是为了明确告诉计算机:

复制代码
1 ❌

错误3:看到 !not_prime 就晕

记住:

复制代码
not_prime[x] = true

意思:

x 不是素数。

所以:

复制代码
!not_prime[x]

就是:

x 是素数。

可以把它翻译成:

复制代码
"不是非素数"

也就是:

复制代码
"是素数"

错误4:只判断 p 是素数

例如:

复制代码
20

看到:

复制代码
7

是素数就直接 ans++,这是错的。

必须同时保证:

复制代码
7 是素数
20-7=13 也是素数

两个都成立,才能算一种方案。


二十四、这道题的"万能思维模板" 🧠

以后遇到类似题目,可以按照这个顺序思考:

第一步:题目要找什么?

复制代码
p + q = n

第二步:能不能枚举一个?

可以。

枚举:

复制代码
p

然后:

复制代码
q = n-p

第三步:怎么快速判断 q?

提前:

复制代码
筛素数

第四步:如何避免重复?

只枚举:

复制代码
p <= n/2

第五步:找到一组合法方案怎么办?

复制代码
ans++;

二十五、最后送大家一句"考场口诀" 🎯

先筛素数,名单做好;
枚举一半,避免重复;
q = n-p,两个都素;
找到一组,答案加一!


相关推荐
小灰灰搞电子1 小时前
C++ final 关键字详解:从虚函数到类继承的完整指南
c++·final
weixin_307779131 小时前
C++代码实现MATLAB中的pade函数功能
开发语言·c++·算法·matlab
神仙别闹2 小时前
基于C++实现的(控制台)学校开课查询系统
开发语言·c++
Dylan的码园2 小时前
A+B问题 进制处理【逻辑控制 运算符】
c++·算法
hetao17338373 小时前
2026-09-27~28 hetao1733837 的刷题记录
c++·算法
2601_962218613 小时前
C++的几种编译器的实现
开发语言·c++
多弗朗皮卡丘3 小时前
C++ stack和queue
开发语言·c++·deque·queue·stack·容器适配器
lengxuenong3 小时前
枚举及其优化学习手册
c++·学习·算法·青少年编程
Benny_Tang3 小时前
P9527 [JOIST 2022] 洒水器 / Sprinkler 题解
c++·算法