简单数论——质数

质数

在 >1 的整数中,如果只包含1和本身两个约数,这个数就被称为质数/素数。

质数的判定——试除法

暴力枚举 \(O(n)\)
bool is_prime(int n)
{
    if(n < 2) return ;
    for(int i = 2; i < n; i++)
        if(n % i == 0) return false;
    return true;
}
简单优化 \(O(\sqrt{n})\)

对任意的d能整除n( d|n ), 显然n除上d也能整除n ( \(\frac{n}{d}\) | n

如n = 12,3是12的约数,4也是12的约数; 2是12的约数,6也是12的约数

所有的约数都是成对出现的,在枚举时可以只枚举每一对中较小的那个,即\(d \leq \frac{n}{d}\)的部分,即\(d \leq \sqrt{n}\)

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

分解质因数 \(O(\log_ax\) ~ \(\sqrt{n})\)

hihoCoder 1750

如n = \(2^{k}\),可能他的时间复杂度是O(1)的

质因数:在数论里是指能整除给定正整数的质数。每个合数都可以写成几个质数相乘的形式,如6=2*3,8=\(2^{3}\),这几个质数就叫做这个合数的质因数。
很轻易就可以得出n中最多只包含一个大于\(O(\sqrt{n})\)的质因子,所以只需要把所有 < \(O(\sqrt{n})\) 的质因子枚举出来。

void divide(int x)
{
    for (int i = 2; i <= x / i; i ++ )
        if (x % i == 0) // 这步成立,i一定是质数
        {
            int s = 0;
            while (x % i == 0) x /= i, s ++ ;
            cout << i << ' ' << s << endl;
        }
    if (x > 1) cout << x << ' ' << 1 << endl;
    cout << endl;
}

筛选质数

hihoCoder 1295


从前往后看,把每一个数的所有倍数全部删掉

第一个数是2,把4,5,8,10,12全部删掉

第二个数是3,把6,9,12全部删掉

第三个数是4,把8,12删掉

如此这么筛选过一遍之后,所有剩下的数就是质数
对于任意一个数x而言,他如果没被删除,就说明p不是2p-1的任意一个数的倍数,即2p-1不存在任何一个p的约数,所以p是一个质数。

朴素做法 O(n\(\ln n\))

当i=2时第一重循环循环了\(\frac{n}{2}\),当n=3时循环了\(\frac{n}{3}\),当n=4时循环了\(\frac{n}{4}\)次,以此类推可知
O(n) = \(\frac{n}{2}\) + \(\frac{n}{3}\) + ... + \(\frac{n}{n}\)

   = n(\(\frac{1}{2}\) + \(\frac{1}{3}\) + ... + \(\frac{1}{n}\))

\(\frac{1}{2}\) + \(\frac{1}{3}\) + ... + \(\frac{1}{n}\)是一个调和级数,当n->\({\infty}\)时,这个调和级数\(\approx\ln n\) + c

c是一个欧拉常数,约等于0.58


简单优化 埃式筛法

实际上并不需要将所有数的倍数全部删掉,只把质数的倍数删掉就可以了
假设剩下一个p,我们并不需要将2p-1全部枚举一遍来判断p是不是质数,只需要把2p-1的质数判断一遍就可以了
2~p-1的所有质数只要不是p的约数,那么p就是一个质因数
当一个数不是质数我们就不需要筛掉他所有倍数,只有他是质数才会筛掉他的倍数

质数定理:1~n当中有\(\frac{n}{\ln n}\)个质数
O(n) = O(n\(\ln n\) \ \(\ln n\)) \(\approx\) O(n)
实际复杂度为O(n \(\log_a\ log_a x\))
当n=\(2^32\)时,\(\log_2 32\) = 32, \(\log_2 \log_2 32\) = 5,所以\(\log_a\ log_a x\)是很小的,与O(n)是一个级别的

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

void get_primes(int n)
{
    for (int i = 2; i <= n; i ++ )
    {
        if (st[i]) continue;
        primes[cnt ++ ] = i;
        for (int j = i + i; j <= n; j += i)
            st[j] = true;
    }
}
线性筛法

\(10^7\)以上线性筛法大约比埃式筛法快一倍

核心思路:每一个数n只会被他的最小质因子筛掉
每一个数只有一个最小质因子,即每个数只会被筛一次,所以他是线性的

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

void get_primes(int n)
{
    for (int i = 2; i <= n; i ++ )
    {
        if (!st[i]) primes[cnt ++ ] = i;
        for (int j = 0; primes[j] <= n / i; j ++ )
        {
            st[primes[j] * i] = true;
            if (i % primes[j] == 0) break; // 这句话执行证明primes[j]一定是i的最小质因子,因为j是从小到大进行的枚举
        }
    }
}

j是从小到大枚举的所有质数,每一次把当前质数和i的乘积筛掉

i%pj == 0
说明primes[j]一定是i的最小质因子,pj也一定是pji的最小质因子
i%pj != 0
由于我们是从小到大枚举,没有枚举到i的最小质因子
说明pj一定小于i的所有质因子,pj也一定是pj
i的最小质因子

一共有两种可能:

  1. i % primes[j] == 0

    • 说明primes[j]一定是i的最小质因子,pj也一定是pj*i的最小质因子
  2. i % primes[j] != 0

    • 由于我们是从小到大枚举,没有枚举到i的最小质因子
    • 说明pj一定小于i的所有质因子,pj也一定是pj*i的最小质因子

不管什么情况,一定有primes[j]是primes[j]*i的最小质因子

任何合数一定会被筛掉,对于任意合数x:

  • 假设primes[j]是x的最小质因子,当i枚举到x \ primes[j] 时,x就会被筛掉
posted @ 2020-10-30 15:34  晓尘  阅读(466)  评论(0)    收藏  举报