题解:学而思编程 幸运数字
【题目来源】
学而思编程:幸运数字
【题目描述】
小猴对数字非常敏感,他非常钟爱质数,因为他的幸运数字也与质数有关。
小猴的幸运数字有两种,假设 \(x\) 是小猴的幸运数字:
(1)\(x\) 是质数;
(2)\(x\) 不是质数,但是 \(x\) 可以是两个质数的积。
现在小猴想要知道区间 \([L,R]\) 中有多少个幸运数字。
【输入】
第一行一个整数 \(T\),表示共有 \(T\) 测试数据;
每组测试数据,一行两个整数 \(L\),\(R\),表示区间 \([L,R]\)。
【输出】
每组测试数据一行,每行一个整数,第 \(i\) 行表示第 \(i\) 组数据中区间 \([L,R]\) 中有多少个幸运数字。
【输入样例】
2
1 6
150 200
【输出样例】
5
23
【核心思想】
-
问题分析:给定区间 \([L, R]\),统计其中"幸运数字"的个数。幸运数字定义为:质数,或两个质数的乘积(包括相同质数的平方)。这是一个埃氏筛 + 双质数积标记 + 前缀和问题,关键在于离线预处理所有幸运数字,将每次查询优化到 \(O(1)\)。
-
算法选择:
- 埃拉托斯特尼筛法:预处理 \([2, N]\) 范围内所有质数
- 双质数积枚举:枚举所有质数对 \((p_i, p_j)\)(\(i \leq j\)),标记其乘积为幸运数字
- 前缀和:
sum[i]表示 \([1, i]\) 中幸运数字的个数,区间查询 \(O(1)\)
-
关键步骤:
- 质数筛预处理:
isPrime[x] = true(\(2 \leq x \leq N\)),isPrime[0] = isPrime[1] = false- 遍历 \(i\) 从 \(2\) 到 \(N\):若
isPrime[i]为真,则标记所有 \(i \times j\)(\(j \geq 2\))为合数
- 收集质数表:
- 遍历 \(i\) 从 \(2\) 到 \(N\):若
isPrime[i]为真,存入prime[++cnt]
- 遍历 \(i\) 从 \(2\) 到 \(N\):若
- 标记双质数积:
- 遍历 \(i\) 从 \(1\) 到
cnt(第一个质数 \(p_i\))- 遍历 \(j\) 从 \(i\) 到
cnt(第二个质数 \(p_j\),允许 \(p_i = p_j\))- 若
prime[i] * prime[j] > N:break(后续更大,提前退出) product[prime[i] * prime[j]] = true:标记该乘积为幸运数字
- 若
- 遍历 \(j\) 从 \(i\) 到
- 遍历 \(i\) 从 \(1\) 到
- 构建前缀和:
- 遍历 \(i\) 从 \(1\) 到 \(N\):
- 若
isPrime[i] || product[i]:sum[i] = sum[i-1] + 1 - 否则:
sum[i] = sum[i-1]
- 若
- 遍历 \(i\) 从 \(1\) 到 \(N\):
- 处理 \(T\) 次查询:
- 读取 \(L, R\)
ans = sum[R] - sum[L-1]- 输出
ans
- 质数筛预处理:
-
时间/空间复杂度:
- 时间复杂度:\(O(N \log \log N + \frac{N}{\ln N} \times \frac{N}{\ln N} + T)\),埃氏筛 \(O(N \log \log N)\),双质数积枚举约 \(O(\frac{N^2}{(\ln N)^2})\)(实际因提前退出远小于此),每次查询 \(O(1)\)
- 空间复杂度:\(O(N)\),
isPrime、product、sum三个布尔/整型数组
-
离线预处理的核心思想:
- 值域固定性:\(N = 10^7\) 固定,与查询无关,所有幸运数字可预先计算
- 质数积枚举优化:内层循环从 \(j = i\) 开始(避免重复),且当乘积超限时
break,利用质数密度递减的特性减少无效计算 - 两类幸运数字统一:质数本身和双质数积通过
||合并,前缀和一次统计 - 查询与计算分离:预处理阶段完成所有 heavy 计算,查询阶段仅 \(O(1)\) 差分,适合多组测试数据
- 适用于"固定值域 + 多区间查询 + 数论性质判定"类问题,离线筛法配合前缀和可将每次查询降至常数时间
【解题思路】

【算法标签】
质数
【代码详解】
#include <bits/stdc++.h>
using namespace std;
const int N = 10000000; // 上限10^7
int T;
bool isPrime[N + 5], product[N + 5]; // isPrime标记素数,product标记两素数乘积
int prime[N + 5], cnt, sum[N + 5]; // prime存储素数,cnt素数个数,sum前缀和
// 埃拉托色尼筛法
void prime_table()
{
memset(isPrime, true, sizeof(isPrime)); // 初始化所有数为素数
isPrime[0] = isPrime[1] = false; // 0和1不是素数
for (int i = 2; i <= N; i++) // 遍历每个数
{
if (isPrime[i] == true) // 如果i是素数
{
for (int j = 2; j <= N / i; j++) // 标记i的所有倍数
{
isPrime[i * j] = false;
}
}
}
}
int main()
{
// 1. 筛出所有素数
prime_table();
// 2. 将素数存入prime数组
for (int i = 2; i <= N; i++)
{
if (isPrime[i] == true)
{
prime[++cnt] = i;
}
}
// 3. 标记所有两个素数(可相同)的乘积
for (int i = 1; i <= cnt; i++) // 第一个素数
{
for (int j = i; j <= cnt; j++) // 第二个素数,从i开始(包括相同素数)
{
if (1ll * prime[i] * prime[j] > N) // 超过范围,提前退出
{
break;
}
product[prime[i] * prime[j]] = true; // 标记乘积
}
}
// 4. 计算前缀和:统计每个数之前满足条件的数的个数
for (int i = 1; i <= N; i++)
{
if (product[i] || isPrime[i]) // 如果是素数或两素数乘积
{
sum[i] = sum[i - 1] + 1;
}
else
{
sum[i] = sum[i - 1];
}
}
// 5. 处理查询
cin >> T; // 查询次数
while (T--)
{
int L, R;
cin >> L >> R; // 查询区间[L,R]
cout << sum[R] - sum[L - 1] << endl; // 前缀和计算区间和
}
return 0;
}
【运行结果】
2
1 6
5
150 200
23
浙公网安备 33010602011771号