



一、先把题目变成一个"小侦探故事" 🔍
假设老师给小明一个数字:
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,两个都素;
找到一组,答案加一!