基础数学知识(未完待续)

本文探讨了质数的基本概念,包括质数定义、唯一分解定理,以及质数判断和筛法。介绍了试除法、分解质因数、埃氏筛法、欧拉筛法,并提到了更高效的Miller-Rabbin算法,该算法基于费马小定理,适用于快速判断大数质性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

0x10 质数

质数基本定理

  1. 质数的定义:只被 1 1 1 和它本身整除的正整数叫做质数。非质数的正整数叫做合数。特别的, 1 1 1 既不是质数也不是合数。

  2. 质数的数量很少。

  3. 只有 2 2 2 是偶素数。

  4. 唯一分解定理:将一个正整数 n n n 分解质因数,有且只有一种方式。形如 n = p 1 c 1 p 2 c 2 … p m c m n = {p_1}^{c_1} {p_2}^{c_2} \dots {p_m}^{c_m} n=p1c1p2c2pmcm 。其中 p i p_i pi 是互不相同的质数。

质数判断

试除法判定质数

枚举 ≤ n \le n n 的每个数,复杂度 O ( n ) O(n) O(n)

改进:枚举 ≤ n \leq \sqrt{n} n 的数,复杂度 O ( n ) O(\sqrt{n}) O(n )

证明:一个数的最小质因子一定小于等于 n \sqrt{n} n
n = p × q n = p \times q n=p×q p ≤ q p \leq q pq
p = n q ≤ n p = \cfrac{n}{q} \leq \sqrt{n} p=qnn

核心代码如下:

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

分解质因数

枚举 1 1 1 n \sqrt{n} n 之间的每个质数,看看能不能整除 n n n 。复杂度 O ( n ) O(\sqrt{n}) O(n )

实现上更加简单。大概不需要解释。

核心代码如下:

void find_factors(int n)
{
    for (int i = 2; i <= n / i; i ++ )
    {
        if (n % i == 0)
        {
            cout << i << ' ';
            int cnt = 0;
            while (n % i == 0)
                cnt ++ , n /= i;
                
            cout << cnt << endl;
        }
    }
    
    if (n > 1) cout << n << ' ' << 1 << endl;
}

质数筛法

筛法的主要思想是快速求出某一范围内的所有质数。利用前面的质数判定,可以做到 O ( n n ) O(n \sqrt{n}) O(nn ) 的复杂度。然而这是远远不够的。我们需要线性或者略高于线性的算法。目前主要的筛法是埃氏筛法和欧拉筛法。

1. 埃氏筛法

当我们枚举到某个质数 p p p 的时候,我们把他所有的倍数 ($p \times 1, p \times 2 \dots $)都设成合数。剩余的就是质数。

由于唯一分解定理,每一个合数都可以被前面的某一个质数筛掉。这保证了算法的正确性。

下面分析一下复杂度:每个质数 p p p 可以筛掉 O ( ⌊ n p ⌋ ) O(\left \lfloor \dfrac{n}{p} \right \rfloor) O(pn) 个数。
这样复杂度就是 O ( ∏ p ∈ P n p ) O(\prod_{p \in P}^{} \dfrac{n}{p}) O(pPpn)。而 ≤ n \leq n n 的质数大约有 log ⁡ n \log n logn 个, ∑ i = 1 n 1 i ≈ log ⁡ n \sum_{i = 1}^{n} \cfrac{1}{i} \approx \log n i=1ni1logn。因此复杂度大约为 O ( n log ⁡ log ⁡ n ) O(n \log \log n) O(nloglogn)

核心代码如下:

void get_primes(int n) {
    // is_prime 表示这个数是不是质数
    // primes 表示质数集合
    for (int i = 2; i <= n; i ++ ) {
        if (!is_prime[i]) primes[ ++ cnt] = i;
        else continue; // 小剪枝:不是质数不往后筛
        for (int j = i + i; j <= n; j += i) // 枚举 i 的倍数并筛掉
            is_prime[j] = true;
    }
}
2. 欧拉筛法

埃氏筛法的弊端是:每个合数不仅仅只被一个数筛过。例如 6 6 6 就被 2 , 3 2, 3 2,3 筛过两次。

那么怎么确保每个数只被筛过一遍呢?思路在代码里了。

void get_primes(int n)
{
    for (int i = 2; i <= n; i ++ )
    {
        if (!st[i]) primes[ ++ cnt] = i; // 如果这个数没有被标记为合数,则为质数
        
        for (int j = 1; primes[j] * i <= n; j ++ ) // 枚举前面已经得到的质数去更新后面的,同时要保证 primes[j] * i <= n, 因为更新大于n的数就没有意义了
        {
            st[i * primes[j]] = true;
            if (i % primes[j] == 0) break; // 小优化
            /*
            1. 当 i % primes[j] != 0时, 说明此时遍历到的 primes[j] 不是 i 的质因子,那么只可能是此时的 primes[j] < i 的
                        最小质因子,所以primes[j] * i的最小质因子就是primes[j];
                        2. 当有 i % primes[j] == 0 时,说明i的最小质因子是 primes[j] ,因此 primes[j] * i的最小质因子也就应该是
                        prime[j],之后接着用 st[primes[j + 1] * i] = true去筛合数时,就不是用最小质因子去更新了,因为 i 有最小
                        质因子 primes[j] < primes[j + 1] ,此时的 primes[j + 1] 不是primes[j + 1] * i 的最小质因子,此时就应该
                        退出循环,避免之后重复进行筛选。
                        */
        }
    }
}

由于每个数只被筛过一次,因此时间复杂度为 O ( n ) O(n) O(n), 为线性复杂度。是目前已知最快的筛法。

其他

Miller_Rabbin 算法

M i l l e r _ R a b b i n Miller\_Rabbin Miller_Rabbin 算法是目前已知最快的质数判断算法。

要学习这个算法,首先要知道费马小定理:

对于任意一个素数 p p p 和任意与 p p p 互质的数 a a a,都有:

a p − 1 ≡ 1 ( m o d   p ) a ^ {p - 1} \equiv 1 (mod\ p) ap11(mod p)

证明请自行百度。。。

那么通过这个柿子,我们是否可以得到,满足该柿子的数 p p p 一定是质数呢?答案是不可以。反例请自行查阅“卡迈克尔数”。

但是卡迈克尔数毕竟很少(在 1 0 1 8 10 ^ 18 1018 范围内是很少的),因此我们可以通过一些方法,降低错误概率。

首先说一下二次探测定理:若有
a 2 ≡ 1 ( m o d   p ) a ^ 2 \equiv 1 (mod\ p) a21(mod p)

则有 a 2 − 1 ≡ 0 ( m o d   p )   ( a + 1 ) ( a − 1 ) ≡ 0 ( m o d   p )   ( a + 1 )   m o d   p = 0 或 ( a − 1 )   m o d   p = 0 a ^ 2 - 1 \equiv 0 (mod\ p) \\\ (a + 1)(a - 1) \equiv 0 (mod \ p) \\\ (a + 1) \ mod\ p = 0 或 (a - 1)\ mod \ p = 0 a210(mod p) (a+1)(a1)0(mod p) (a+1) mod p=0(a1) mod p=0

因此 a = ± 1 a = \pm 1 a=±1

这样,如果有 a 2 t ≡ 1 ( m o d   p ) a ^ {2t} \equiv 1 (mod \ p) a2t1(mod p) a t m o d    p ≠ ± 1 a^t \mod p \ne \pm 1 atmodp=±1 (当然,这个 − 1 -1 1 应该表示成 a − 1 a - 1 a1),那么这个数就不是素数。(有上面的二次探测定理易得)。

那么,我们找到一个质数 a a a,让它变成 a t , a 2 t , a 4 t … a ^ t, a ^ {2t}, a ^ {4t} \dots at,a2t,a4t,只要有一个不满足上面提到的判断条件,就可以说明 p p p 不是质数。

据科学家统计,在 O I OI OI 中,极小的错误概率不会发生。

核心代码:

int test[] = {2, 3, 5, 7, 11, 13, 17, 19, 23};
bool is_prime(int p) {
    if (p == 1) return false;
    int k = 0, t = p - 1;
    while (!(t & 1)) t >>= 1, k ++ ;
    // 将 p - 1 拆成 2 ^ k * t 的形式
    for (int i = 0; i < 9; i ++ ) {
        if (p == test[i]) return true;
        LL a = qpow(test[i], t, p), ne = a;
        for (int j = 1; j <= k; j ++ ) {
            ne = a * a % p;
            if (ne == 1 && a != 1 && a != p - 1) return false;
            a = ne;
        }
        if (a != 1) return false; // 费马小定理对 p 不成立也说明 p 不是质数
    }
    return true;
}

未完待续 T o   b e   c o n t i n u e d To\ be\ continued To be continued

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值