数论 -- 质数判定及其筛法求解

文章目录

  • 基本概念
  • 素数的判定
    • 1. BF做法 -- O(n)
    • 2. 优化做法 -- O(√n)
  • 分解质因数
    • 试除法 -- O(√n)
  • 筛素数及其改进
    • 埃氏筛 -- O(n log logn)
    • 线性筛(欧拉筛)-- O(n)

基本概念

设 a,b 时两个整数,且 b ≠ 0,如果存在整数c,使得 a = b * c,则称 a 被 b 整 除 a 被 b\color{Orange}整除 ab b 整 除 b \color{Orange}整除 ba,即 a 是 b 的倍数,b 是 a 的因子。

  • b 整除 a,记作 b | a
  • b 不整除 a,记作 b ∤ \nmid a

素数(质数)

​ 如果一个整数 a 大于 1 且只能被 1 和它自己整除,则称a 为素数

合数

​ 如果a 大于 1 且不是素数,则称a 为合数

素数的判定

1. BF做法 – O(n)

思路:如果 x 除了它本身还能被其他数整除,那么它不是素数,反之则是素数

代码模板

bool isPrime(int x)
{
    if(x < 2) return false;
    
    
    for(int i = 2; i < x; i ++ )
        if(x % i == 0)
             return false;
  
    return true;
}

2. 优化做法 – O(√n)

证明

  • 若 a 是 一个合数,则存在素数 p , 使得 p | a

  • d ∣ x d | x dx ,显然 x d ∣ x \frac{x}d | x dxx,即 x 的质因子成对存在,故只需枚举每对中较小的那一个就行,即 x < = x d x <= \frac{x}d x<=dx x < = √ n x <= √n x<=n

代码模板

bool is_prime(int x)
{
    if(x < 2) return false;
    
    for(int i = 2; i <= x / i; i ++ )// 不写成 i <= sqrt(x)是为了防止爆 int
       if(x % i == 0) 
           return false;
}

分解质因数

试除法 – O(√n)

思路

把一个合数分解成若干个质因数的乘积的形式,即求质因数的过程叫做分解质因数

算 术 基 本 定 理 \color{Green}算术基本定理

设 a > 1 ,则
a = p 1 α 1 ∗ p 2 α 2 ∗ ⋅ ⋅ ⋅ p k α k a = p_1^{α_1}*p_2^{α_2}*···p_k^{α_k} a=p1α1p2α2pkαk
其中

p 1 ,   p 2 ,   ⋅ ⋅ ⋅ ⋅ ,   p k p_1,\ p_2,\ ····,\ p_k p1, p2, , pk是不同的素数

α 1 ,   α 2 ,   ⋅ ⋅ ⋅ ⋅ ,   α k α_1,\ α_2,\ ····,\ α_k α1, α2, , αk是正整数

并且在不记顺序的情况下,该表示是唯一的

30 = 2 × 3 × 5 30 = 2 × 3 × 5 30=2×3×5

88 = 2 3 × 11 88 = 2^3 × 11 88=23×11

代码模板

void divide(int x)
{
    for(int i = 2 ; i <= x/i; i ++ )
        if(x % i == 0)
        {
            int  s = 0 ; // 各种 指数 α
            
            while(x % i == 0) x /= i , s ++;
            
            cout<< i <<" " << s << "\n";
        }
    
    if(x > 1) cout<< x << " " << 1 <<"\n"; // 只剩余 它本身 和 1 
    
}

筛素数及其改进

埃氏筛 – O(n log logn)

埃拉托色尼选筛法,是古希腊数学家埃拉托色尼提出的一种筛选法。

该筛法基于这样的想法:任意大于1的正整数 x 的倍数 2 x , 3 x , . . . 2x,3x,... 2x,3x,...都不是质数。根据质数的定义,上述命题显然成立。

从 2 开始,由小到达扫描每个 x ,把它的倍数 x , ⋯ , ⌊ N / x ⌋ ∗ x x,⋯,⌊N/x⌋∗x x,,N/xx标记为合数。

每当扫描到一个数时,若它尚未被标记,则它不能被 2 ∼ x − 1 2∼x−1 2x1之间的任何数整除,该数就是质数。

Eratosthenes 筛法过程如下:

数论 -- 质数判定及其筛法求解_第1张图片

该算法复杂度为 ∑ 质 数 p ≤ x x p = O ( n l o g l o g n ) \sum_{质数p ≤ x }\frac{x}p = O(nloglogn) pxpx=O(nloglogn)。效率已经非常接近线性,是最常用的质数筛法

代码模板

int primes[N], cnt; // primes[ ] 存储所有素数,cnt 记录 筛到哪了
bool st[N]; //st[x] 存储 x 是否被 筛掉
    
void get_primes(int x)
{
    for(int i = 2; i <= x; i ++ )
    {
        if(st[i]) continue; // 被筛过了,下一个
        
        primes[i] = true;
       	for(int j = i + i; j <= n; j += i)
            	st[j] = true;
    }
}

线性筛(欧拉筛)-- O(n)

埃氏筛仍然做了许多重复工作。多个素数可能重复筛去同一个合数,于是有了欧拉筛。

原理

  • 核心:保证 x 只会被最小质因子筛掉
  • 如果想达到线性,需要满足两个条件:
    1. 每个数只被筛一次
    2. 每个数都会被筛到
  • 欧拉筛通过维护素数表,让每个数都只被它的最小质因子筛去

理解

  •   p r i m e s [   j   ] \ primes[\ j\ ]  primes[ j ]可以 整除   i \ i  i 时,即 i f ( i   %   p j = = 0 ) if( i \ \% \ pj == 0) if(i % pj==0),就应该退出,这样就保证每一个数都只被最小的质因子筛了一次

    • 具体原理: 对于 一个 被筛的数 x 可以表示 为 x = p r i m e s [   j   ] ∗ i x = primes[\ j\ ] * i x=primes[ j ]i
  • p r i m e s [   j   ] primes[\ j\ ] primes[ j ]不可以 整除   i \ i  i 时,即 i f ( i   %   p j   ! = 0 ) if( i \ \% \ pj \ != 0) if(i % pj !=0),则

    • 对于   i \ i  i 的最小质因子 p > p r i m e s [   i   ] p >primes[\ i\ ] p>primes[ i ](因素数表从小到大遍历),且因为 p > p r i m e s [   j   ] p >primes[\ j\ ] p>primes[ j ],即 pj 一定小于   i \ i  i 的所有质因子,pj 也一定是 x = p r i m e s [   j   ] ∗ i x =primes[\ j \ ] * i x=primes[ j ]i 的最小质因子

    • 此外是否还会存在被重复筛的情况,如果 x 还存在比 p r i m e s [   j   ] primes[\ j\ ] primes[ j ] 大的质因子,

      则 x 可以写作

      • x = p r i m e s [   j + i   ] ∗ (   i   −   v a r ) x = primes[\ j + i \ ] * (\ i \ -\ var ) x=primes[ j+i ]( i  var) ( j   +   i ) 表 示 比   j 大 的 某 个 数 , i   −   v a r 表 示 比 i 小 的 某 个 数 值 (j \ + \ i)表示比\ j大的某个数,i \ - \ var表示比 i 小的某个数值 (j + i) ji  vari

      但是我们的   i \ i  i是从小到大枚举的,   x \ x  x 被筛过之后不可能出现   i   −   v a r \ i\ −\ var  i  var的情况。
      还需要使得每个合数都被筛掉,对于一个合数 x 被筛时一定是   x   =   p r i m e s [   j   ]   ∗   i \ x\ =\ primes[\ j\ ]\ ∗ \ i  x = primes[ j ]  i,遍历 x x x之前一定遍历过了 i i i,则$x 一 定 会 通 过 i 一定会通过 i i被筛掉。

为什么线性

对于一个合数 x,假设 pj 是 x 的最小质因子,在 i 枚举到 x 之前一定会枚举到 x / pj,故在循环语句中 x 中的合数一定会被筛掉,又每个数只会被筛一次,所以整个过程是线性的

代码模板

int primes[N], cnt; // primes[] 存储所有素数,cnt 记录 筛到哪了
bool st[N];//st[x] 存储 x 是否被 筛掉

void get_primes(int x)
{
    for(int i = 2 ; i <= x; i ++ )
    {
        if(!st[i]) primes[cnt++] = i; // 最小质因子
        
        for(int j = 0; primes[j] <= x/i; j ++ )
        {
            st[primes[j] * i] = true;
            if(i % primes[j] == 0) break;
        }
    }
}

你可能感兴趣的:(算法基础知识,算法,c++,抽象代数)