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