质数

定义

质数,是一个只能被 \(1\) 和它自身整除的自然数,且大于 \(1\) 。简单来说,质数是一个大于 \(1\) 的整数,除了 \(1\) 和它自己以外,不能被其他任何数整除。

分布

\(N\) 以内质数个数:

\[F(N)\approx \frac{N}{\ln N} \]

即每 \(\ln N\) 个数中就有可能出现一个质数。
所以在 \(x\le10^{14}\) 时,暴力寻找比 \(x\) 大(小)的第一个质数时可在一秒内查询到

判定

试除法

不难发现一个数的因子是成对出现的,即若 \(d_1\ |\ x\ (d_1\ge\sqrt x)\) ,则有 \(d_2 = \large{\frac{x}{d_1}}\le\sqrt x\)

所以我们只需要扫描 \(2\sim \sqrt N\) 的所有整数 \(x\) ,若 \(\forall\ x\ \nmid N\)\(N\) 是质数,否则 \(N\) 是合数

代码

bool is_prime(int n) {
    if (n < 2) return false;
    for (int i = 2; i * i <= n; i++) 
        if (!(n % i)) 
            return false;
    return true;
}

筛法

\(\text{Eratosthenes}\) 筛法

\(\text{Eratosthenes}\) 筛法通过反复标记合数(非质数)来筛选出质数。这个算法的时间复杂度为 \(\mathcal{O}(n \log \log n)\)

\(n\le 10^7\) 埃氏筛时间复杂度在 \(\mathcal{O}(4\times10^7)\) 左右,埃氏筛可以处理,但是常数过大时可能 TLE

代码

void primes(int n) {
    memset(v, 0, sizeof(v)); // 合数标记
    for (int i = 2; i <= n; i++) {
        if (v[i]) continue;
        cout << i << '\n'; // i 是质数
        for (int j = 1; j <= n / i; j++) v[i * j] = 1;
    }
}

\(\text{Euler}\) 筛法(欧拉筛法或线性筛法)

不难发现 \(\text{Eratosthenes}\) 筛法仍然会重复标记合数,例如 \(12\) 会被 \(2\)\(3\) 标记,在标记 \(2\) 的倍数时, \(12 = 2 \times 6\) ,在标记 \(3\) 的倍数时, \(12 = 3 \times 4\) 。本质是因为我们无法用确定的方式标记 \(12\) ,所以我们尝试只用每个数的最小质因子标记,即让 \(12\) 只被 \(2\) 标记。

线性筛法通过“从小到大累积质因子”的方式标记合数。设数组 \(v\) 记录每个数的最小质因子,我们通过以下方式维护 \(v\)

  1. 依次考虑 \(2\sim N\) 之间的每一个数 \(i\)
  2. \(v_i = 0\) ,说明 \(i\) 是质数,把它记录下来。
  3. 扫描不大于 \(v_i\) 的每个质数 \(p\) ,令 \(v_{i\times p} = p\)

算法的关键在于步骤 \(3\) 中的不大于,这样可使任意的 \(i\) 只被它的最小质因子标记,可以尝试用反证法证明。

证明:
假设当前枚举到 \(i\)\(i\) 的最小质因子为 \(v_i\)\(j > v_i\)
当我们尝试标记 \(i \times j\) 时, \(i \times j = v_i \times \large\frac{i}{v_i}\) ,此时就不满足被 \(i\times j\) 的最小质因子标记。

证毕

代码

    for (int i = 2; i <= n; i++) {
        if (!v[i]) v[i] = i, primes.push_back(i); // i 是质数
        // 给当前的数 i 乘上一个最小质因子
        for (int p : primes) {
            // i 有比 p 更小的质因子,或者超出 n 的范围 停止循环
            if (p > v[i] || p * i > n) break;
            // p 是合数 i * p 的最小质因子
            v[i * p] = p;
        }
    }

质因数分解

算数基本定理

任何一个大于 \(1\) 的正整数 \(N\) 都能唯一分解为有限个质数的乘积,可写作:

\[N = p_1^{c_1}\times p_2^{c_2}\times \cdots \times p_m^{c_m} = \prod_{i=1}^{m}p_i^{c_i} \]

其中:\(c_i \in \mathbb{N}_+\)\(p_i\) 都是质数,并且满足 \(p_1 < p_2 < \cdots<p_m\)

由此可得: \(N\) 至多只有一个大于 \(\sqrt N\) 的质因子,考虑反证法。

证明:
\(p_i,\ p_j\) 分别为 \(N\) 的两个不同的质因子且 $p_i>\sqrt N,\ p_j>\sqrt N $ 。
所以 \(p_i\times p_j > N\) , 与 \(p_i^{c_i}\times p_j^{c_j}\le N\) 矛盾。

证毕

试除法

试除法 是一种用于分解正整数为质因数乘积的基本方法。基本思路是:

  1. 从最小的质数开始 ,逐一尝试除以待分解的整数。
  2. 如果当前质数能整除该整数,则记录下该质数,并将整数除以该质数。
  3. 重复上述过程,直到待分解的整数为 1。

不难发现,进行以上算法步骤需要从 \(2\sim n - 1\) 进行试除,效率较低。

已知大于 \(\sqrt n\) 的质因子至多只有一个,所以可通过下面的方式优化:

  • 只检查到 \(\sqrt n\)

代码

void divide(int n) {
    m = 0;
    for (int i = 2; i * i <= n; i++) 
        if (!(n % i)) { // i 是质数
            p[++m] = i, c[m] = 0;
            while (!(n % i)) n /= i, c[m]++;
        }
    if (n > 1) // n 是质数
        p[++m] = n, c[m] = 1;
    for (int i = 1; i <= m; i++) cout << p[i] << '^' << c[i] << '\n';
}
posted @ 2025-02-07 10:04  ZaleClover  阅读(114)  评论(0)    收藏  举报