题解:洛谷 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

【核心思想】

  1. 问题分析:给定 \(n\)\(B\),统计 \([1, n]\) 中最大质因子不超过 \(B\) 的正整数个数,即 \(B\)-smooth 数的个数。这是一个质数筛法 + 最大质因子预处理问题,核心在于高效计算每个数的最大质因子,然后线性统计满足条件的数。

  2. 算法选择

    • 线性筛(欧拉筛):在筛质数的同时,利用每个合数只被最小质因子筛一次的性质,递推计算最大质因子
    • 埃氏筛替代方案:用质数的倍数更新最大质因子,每次遇到更大的质因子时覆盖更新
  3. 关键步骤

    • 预处理\(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\)
  4. 时间/空间复杂度

    • 时间复杂度:\(O(n)\)(线性筛)或 \(O(n \log \log n)\)(埃氏筛)
    • 空间复杂度:\(O(n)\),最大质因子数组
  5. 筛法与最大质因子的核心思想

    • 最大质因子的递推性:对于合数 \(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
posted @ 2026-07-17 15:40  团爸讲算法  阅读(8)  评论(0)    收藏  举报