题解:洛谷 B3969 B-smooth数
【题目来源】
洛谷:B3969 [GESP202403 五级] B-smooth 数 - 洛谷
【题目描述】
小杨同学想寻找一种名为 \(B\)-smooth 数的正整数。
如果一个正整数的最大质因子不超过 \(B\),则该正整数为 \(B\)-smooth 数。
小杨同学想知道,对于给定的 \(n\) 和 \(B\),有多少个不超过 \(n\) 的 \(B\)-smooth 数。
【输入】
第一行包含两个正整数 \(n\) 和 \(B\),含义如题面所示。
【输出】
输出一个非负整数,表示不超过 \(n\) 的 \(B\)-smooth 数的数量。
【输入样例】
10 3
【输出样例】
7
【核心思想】
-
问题分析:给定 \(n\) 和 \(B\),统计 \([1, n]\) 中最大质因子不超过 \(B\) 的正整数个数,即 \(B\)-smooth 数的个数。这是一个质数筛法 + 最大质因子预处理问题,核心在于高效计算每个数的最大质因子,然后线性统计满足条件的数。
-
算法选择:
- 线性筛(欧拉筛):在筛质数的同时,利用每个合数只被最小质因子筛一次的性质,递推计算最大质因子
- 埃氏筛替代方案:用质数的倍数更新最大质因子,每次遇到更大的质因子时覆盖更新
-
关键步骤:
- 预处理(\(getPrime\)):
- 初始化 \(maxPrime[1] = 1\)
- 线性筛版本:遍历 \(i\) 从 \(2\) 到 \(MAX\):
- 若 \(i\) 是质数:\(maxPrime[i] = i\)
- 遍历质数 \(prime[j]\),标记 \(i \times prime[j]\) 为合数:
- \(maxPrime[i \times prime[j]] = \max(prime[j], maxPrime[i])\)
- 若 \(i \bmod prime[j] = 0\):\(break\)
- 埃氏筛版本:遍历 \(i\) 从 \(2\) 到 \(n\):
- 若 \(i\) 是质数:\(maxPrime[i] = i\),并遍历所有倍数更新 \(maxPrime[j] = i\)
- 统计答案:遍历 \(i\) 从 \(1\) 到 \(n\),若 \(maxPrime[i] \leq B\):\(ans \leftarrow ans + 1\)
- 输出结果:\(ans\)
- 预处理(\(getPrime\)):
-
时间/空间复杂度:
- 时间复杂度:\(O(n)\)(线性筛)或 \(O(n \log \log n)\)(埃氏筛)
- 空间复杂度:\(O(n)\),最大质因子数组
-
筛法与最大质因子的核心思想:
- 最大质因子的递推性:对于合数 \(x = i \times p\)(\(p\) 为质数),\(maxPrime[x] = \max(p, maxPrime[i])\)。线性筛中每个合数被其最小质因子唯一分解,因此该递推正确
- 埃氏筛的覆盖更新:埃氏筛从小到大枚举质数,每个合数会被其所有质因子访问,但由于质数递增,最后一次更新一定是最大的质因子,因此 \(maxPrime[j] = i\) 正确
- \(1\) 的特殊处理:\(1\) 没有质因子,定义 \(maxPrime[1] = 1\),确保 \(1\) 总是被计入(\(1 \leq B\))
- 预处理查询优化:先预处理 \([1, MAX]\) 范围内所有数的最大质因子,然后对每个查询 \(O(n)\) 统计,适合多组查询场景
- 适用于质因子分析、数论筛法预处理类问题
【算法标签】
普及- #质数
【代码详解】
#include <bits/stdc++.h>
using namespace std;
int n, B, ans, cnt;
int maxPrime[1000005], prime[1000005], notPrime[1000005];
// 欧拉筛法预处理所有数的最大质因子
void getPrime() {
prime[1] = 1; // 1不是质数
maxPrime[1] = 1; // 1的最大质因子设为1
for (int i = 2; i <= 1000000; i++) {
if (!notPrime[i]) { // 如果是质数
prime[cnt++] = i; // 存入质数数组
maxPrime[i] = i; // 质数的最大质因子是其本身
}
// 用当前数和已知质数筛去合数
for (int j = 0; j < cnt && i * prime[j] <= 1000000; j++) {
notPrime[i * prime[j]] = 1; // 标记为合数
// 计算i*prime[j]的最大质因子
maxPrime[i * prime[j]] = max(prime[j], maxPrime[i]);
// 关键优化:保证每个合数只被最小质因子筛一次
if (i % prime[j] == 0) {
break;
}
}
}
}
int main() {
cin >> n >> B; // 输入范围n和阈值B
getPrime(); // 预处理所有数的最大质因子
// 统计1到n中最大质因子不超过B的数的个数
for (int i = 1; i <= n; i++) {
if (maxPrime[i] <= B) {
ans++;
}
}
cout << ans << endl; // 输出结果
return 0;
}
#include <bits/stdc++.h>
using namespace std;
int n, B, cnt, cur;
int maxPrime[1000005], prime[1000005];
bool flag[1000005];
// 埃拉托斯特尼筛法(埃氏筛)预处理最大质因子
void getPrime() {
memset(flag, true, sizeof(flag)); // 初始化为true,表示都是质数
flag[0] = flag[1] = false; // 0和1不是质数
maxPrime[1] = 1; // 1的最大质因子设为1
for (int i = 2; i <= n; i++) {
if (flag[i]) { // 如果i是质数
prime[++cur] = i; // 存入质数数组
maxPrime[i] = i; // 质数的最大质因子是其本身
// 标记i的所有倍数为合数
for (int j = 2 * i; j <= n; j += i) {
flag[j] = false; // 标记为合数
maxPrime[j] = i; // 更新其最大质因子为i
}
}
}
}
int main() {
cin >> n >> B; // 输入范围n和阈值B
getPrime(); // 预处理最大质因子
// 统计1到n中最大质因子不超过B的数的个数
for (int i = 1; i <= n; i++) {
if (maxPrime[i] <= B) {
cnt++;
}
}
cout << cnt << endl; // 输出结果
return 0;
}
【运行结果】
10 3
7
浙公网安备 33010602011771号