质数浅谈

质数浅谈 Yu36c343 · 2019-08-10 22:52:06 · 个人记录 这篇博文讲的只是质数入门级的东西. OI数论的开始,就是素数. 素数的一些性质成为OI出题的热

质数浅谈

质数浅谈

Yu36c343

·

2019-08-10 22:52:06

·

个人记录

这篇博文讲的只是质数入门级的东西.

OI数论的开始,就是素数.

素数的一些性质成为OI出题的热点,于是我们不得不学习令人心烦的数论.

定义

众所周知

若一个正整数无法被除了 1 和它自身之外的任何自然数整除,则称该数为质数(prime number,又称素数),否则称该数为合数(composite number).

特别的,1 既不是质数,也不是合数.

由定义,2 是最小的质数,也是唯一的偶质数.

为了方便描述,在本文中,有时会用 \mathbb{P} 指代质数集合.

一、质数相关定理

然而并没有什么用QwQ.

质数无穷定理

我也不知道它叫什么,随便起了个名字qwq.

质数的个数是无限的.

读者自证不难.可以参考《几何原本》中的证明.

质数分布定理

预警:这一方面笔者不能写出很严谨的说明

在自然数集合中,质数分布较为稀疏.

对正实数 x,定义 \pi(x) 为不大于 x 的质数的个数,有:

\pi(x)\approx \frac{x}{\ln x}

推论1 :从不大于 n 的自然数随机选一个,它是素数的概率约为 \frac{1}{\ln n}.

推论2 :第 n 个质数的渐进估计为 n \ln n.

二、质数的判定

问题

给定一个大于 1 的正整数 N,判定其是否为质数.

试除法 及其常数优化

引理一

若一个正整数 N 为合数,则 [2,\,\sqrt{N}] 中存在整数 i,使得 i\mid N.

证明: 显然

由合数定义,必定存在正整数 j 满足 j\mid N,\,j\in [2,\,N-1];

假设原命题不成立,则 j\in[\sqrt{N}+1,\,N-1];而由于 j\mid N,显然,它们的比 i=\frac{N}{j} 也满足 i\mid N,且 i\in[2,\,\sqrt{N}];这与假设矛盾.故假设不成立,原命题成立.

证毕.

算法思想

由引理一,我们只需要扫描 [2,\,\sqrt{N}] 中的所有整数,依次检查它们能否整除 N,若都不能整除,则 N 为质数,否则 N 为合数.

C++ code

bool if_prime (int n) {

if (n == 1) return false;

for (int i = 2, i * i <= n; ++i)

if (!(n % i)) return false;

return true;

}

试除法的时间复杂度为 \Theta(\sqrt{N}),是最简单也是最常用的算法.

引理二

\forall p\in\complement_{\{2,3\}}\mathbb{P},\,p\in\{x\mid x=6k\pm1,\,k\in\mathbb{N_+}\}

证明:

显然,形如 6k、6k+2、6k+3、6k+4 (k\in\mathbb{N})的自然数均能为 2 或 3 整除.

由此,除 2 和 3 本身外的所有质数,只可能为 6k\pm1\,(k\in\mathbb{N_+}) 的样式.

思想

由引理二,我们只需特判 2 和 3,在 [2,\,\sqrt{N}] 中枚举 6k\pm1 即可.

C++ code

bool if_prime (int n) {

if (n == 1) return false;

if (n == 2 || n == 3) return true;

if (n % 6 != 1 && n % 6 != 5) return false;

for (int i = 5, i * i <= n; i += 6)

if (!(n % i && n % (i + 2))) return false;

return true;

}

时间复杂度降至 \Theta(\frac{\sqrt{N}}{3}),可以防止被卡常(直接降 2/3).

有一些效率更高的随机性算法(例如 Miller-Robbin),有较小的概率把合数判定为质数,但多次判定合起来的错误概率趋近于零,但超出了我们的讨论范围 (其实是我还没有熟练掌握),在此只捎带一提.

三、质数的筛选

问题:

给定正整数 N,求出 [1,\,N] 中的所有质数.

Eratosthenes 筛选法

出于显而易见的原因, 通常被称为埃氏筛.

Eratosthenes(BC276-BC194),希腊哲学家、 数学家,约 BC240 计算地球的直径误差小于 2\%,为当时世界最早最准确 (逐渐偏离主题).

算法思想

由质数定义可知,任一整数 x 的倍数 \{2x,3x,\cdots\} 都不为质数.

我们从 2 开始,从小到大扫描每个数,将其倍数 \{kx\, \mid\,k\in\mathbb{N^* },\,1

如果还不能理解,就摆出这幅著名的动图.

参照上图,在标记的过程中,\{kx\mid\,k\in\mathbb{N^* },\,1

C++ code

int m, p[M]; // m for number of primes

bool vis[N];

void primes (int n) {

// memset (vis, 0x00, sizeof vis), m = 0;

vis[1] = true;

for (int i = 2; i <= n; ++i)

if (!vis[i]) {

p[++m] = i;

for (int j = i * i; j <= n; j += i)

vis[j] = true;

}

}

Eratosthenes 筛选法的时间复杂度为 \Theta(\sum_{p

线性筛法

似乎也被称为欧拉筛吧,这个名字有什么依据我就不知道了.

Eratosthenes 筛选法 浪费的时间

标记一个数 x 为合数,仅当我们确定两个小于 x 的数的乘积(记为 i\cdot j)等于 x.

检查 Eratosthenes 筛选法,我们在标记 \{k\cdot x\, \mid\,k\in\mathbb{N^* },\,x\le k\le\lfloor \frac{N}{x} \rfloor \} 时,k 绝大多数情况下是已筛出的质数的倍数(例如 3\times 4,由于 4 是更小质数 2 的倍数,12 在扫描 3 时被标记之前已经在扫描 2 时被标记),此时的 k\cdot x 可以确定先前已被标记.在扫描更大的质数时,这种情况出现的频率大大增加,这就是 Eratosthenes 筛选法在 N 较大时浪费巨额时间的原因.

对于一个合数 x,在 i\cdot j 中 i 为最小质因子时第一次被标记.由此不难想到避免重复标记的方法,就是找到一个确定的 i\cdot j.不难想到,只要确定 i 是 x 的最小质因子即可(即 j 的最小质因子不小于 i).

算法思想

筛出一个质数 i 后去枚举 j,这个枚举过程实现难度极大,因为要保证 j 的最小质因子不大于 i(如筛出 7 之后,要保证 2\nmid j、3\nmid j、5\nmid j),这种实现方式我们直接放弃.

相反,如果我们把 j 放到外层循坏,枚举小于其最小质因子的质数 p_i,这个过程不难实现:一旦枚举到的 p_i 成为 j 的因子,即说明 p_{i+1} 大于 j 的最小质因子(j 的最小质因子即 p_i),不会成为 p_{i+1}\cdot j 的最小质因子(p_{i+1}\cdot j 的最小质因子即 j 的最小质因子 p_i).以此类推,一旦发现 p_i\mid j 时无需枚举 p_{i+1},考虑下一个 j 即可.

如果仍感到理解困难,请读者参照下面的代码模拟一两次.

C++ code

int m, p[M];

bool vis[N];

void primes (int n) {

// memset (vis, 0x00, sizeof vis), m = 0;

vis[1] = true;

for (int j = 2; j <= n; ++j) {

if (!vis[j]) p[++m] = j;

for (int i = 1; i <= m && p[i] * j <= n; ++i) {

vis[p[i] * j] = true;

if (!(j % p[i])) break;

}

}

}

每个合数 p_i\times j 只会被它的最小值因子 p_i 筛一次,时间复杂度为 \Theta(N).

四、质因数分解

算数基本定理

一称唯一分解定理

任一大于 1 的正整数 N 的正整数可被唯一分解为有限个质数的成绩,写作:

N=\prod_{1\le i\le m}{p_i}^{c_i}\quad(c_i\in\mathbb{N^* },\,p_i\in\mathbb{P},\,p_1

其中,\prod_{i=1}^{m}{p_i}^{c_i} 称为 N 的标准分解式.

问题

给定一个大于 1 的正整数 N,求其标准分解式.

试除法

算法思想

结合 Eratosthenes 筛选法的思想,扫描 [2,\,\sqrt{N}] 的每个数 d,若 d\mid N,则除去 N 中的所有因子 d,同时累计除去 d 的个数.

因为一个合因子在扫描到这个合数之前必定已从 N 中除去,上述过程中得到的 d 一定为质数(问题所求的 p_i),除去 d 的次数即为 c_i.

特别地,若得不到 d,说明 N 本身即为质数,无需分解.

这样就得到了 N 的标准分解式.

C++ code

int m, p[M], c[M];

void divide (int n) {

// m = 0;

for (int i = 2; i * i <= n; ++i)

if (!(n % i)) {

p[++m] = i;

while (!(n % i)) ++c[m];

}

if (n > 1) p[++m] = n, ++c[m];

}

易知时间复杂度为 \Theta(\sqrt{N}).

Pollard's Rho 是一个效率更高的算法,不过明显超出了我们的讨论范围 (然而其实是我不会),在此捎带一提.

小结

本文到此结束,但显然这只是很初步的内容,以后在学习的过程中会逐渐更新新的算法和新的理解,并加上一些代表性的例题.

written by Yu-343

相关推荐