数论
素数
简介
一个大于 1 的自然数,除了 1 和它自身外,不能被其他自然数整除的数叫做素数;否则称为合数
- 1既不是质数也不是合数
判断方法
暴力 · $O(\frac{n}{2})$
1 | bool isPrime(a) |
Miller-Rabin 素性测试 · $ O(k \ log^{3}n)$
Fermat 素性测试
我们可以根据费马小定理得出一种检验素数的思路:
它的基本思想是不断地选取在 [2, n - 1] 中的基 a,并检验是否每次都有 $a^{n - 1} \ \equiv \ 1 \ (mod \ n)$
1 | bool millerRabin(int n) |
很遗憾,费马小定理的逆定理并不成立,换言之,满足了 $a^{n - 1} \ \equiv \ 1 \ (mod \ n)$,a 也不一定是素数。
卡迈克尔数
上面的做法中随机地选择 a,很大程度地降低了犯错的概率。但是仍有一类数,上面的做法并不能准确地判断。
对于合数 n,如果对于所有正整数 a,a 和 n 互素,都有同余式 $a^{n - 1} \ \equiv \ 1 \ (mod \ n)$ 成立,则合数 n 为卡迈克尔数(Carmichael Number),又称为费马伪素数。
比如,561 = 3 x 11 x 17 就是一个卡迈克尔数。
而且我们知道,若 n 为卡迈克尔数,则 m = 2n - 1 也是一个卡迈克尔数,从而卡迈克尔数的个数是无穷的。
二次探测定理
如果 p 是奇素数,则 $x^{2} \ \equiv \ 1 \ (mod \ p)$ 的解为 $x \ \equiv \ 1 \ (mod \ p)$ 或者 $x \ \equiv \ p-1 \ (mod \ p)$ 。
要证明该定理,只需将上面的方程移项,再使用平方差公式,得到 $(x + 1)(x - 1) \ \equiv \ 0 \ (mod \ p)$,即可得出上面的结论。
实现
根据卡迈克尔数的性质,可知其一定不是 pe。
不妨将费马小定理和二次探测定理结合起来使用:
将 $a^{n - 1} \ \equiv \ 1 \ (mod \ n)$ 中的指数 n - 1 分解为 $n \ - \ 1 \ = \ u \ \times \ 2^{t}$,在每轮测试中对随机出来的 a 先求出 $a^{u} \ (mod \ n)$,之后对这个值执行最多 t 次平方操作,若发现非平凡平方根时即可判断出其不是素数,否则通过此轮测试。
模板
1 | bool millerRabbin(int n) |
最大公约数 · GCD
简介
最大公约数即为 Greatest Common Divisor,常缩写为 gcd。
在素数一节中,我们已经介绍了约数的概念。
一组数的公约数,是指同时是这组数中每一个数的约数的数。而最大公约数,则是指所有公约数里面最大的一个。
求法
欧几里得算法 · $O(\log n)$
1 | int gcd(int a, int b) |
多个数的最大公约数
答案一定是每个数的约数,那么也一定是每相邻两个数的约数。我们采用归纳法,可以证明,每次取出两个数求出答案后再放回去,不会对所需要的答案造成影响。
1 | int a[MAXN]; |
最小公倍数 · LCM
1 | int lcm(int a, int b) |
多个数的最小公倍数
可以发现,当我们求出两个数的 gcd 时,求最小公倍数是 $O(1)$ 的复杂度。那么对于多个数,我们其实没有必要求一个共同的最大公约数再去处理,最直接的方法就是,当我们算出两个数的 gcd,或许在求多个数的 gcd 时候,我们将它放入序列对后面的数继续求解,那么,我们转换一下,直接将最小公倍数放入序列即可。
1 | int a[MAXN]; |
欧拉函数
定义
欧拉函数(Euler’s totient function),即 $\phi(n)$,表示的是小于等于 n 和 n 互质的数的个数。
比如说 $\phi(1) = 1$。
当 n 是质数的时候,显然有 $\phi(n) = n - 1$。
性质
欧拉函数是积性函数。
积性是什么意思呢?如果有 $gcd(a, \ b) \ = \ 1$,那么 $\phi(a \times b) = \phi(a) \times \phi(b)$。
特别地,当 n 是奇数时 $\phi(2n) = \phi(n)$。
$n = \sum_{d|n} \phi(d)$
若 n = pk,其中 p 是质数,那么 $\phi(n) = p^{k} - p^{k-1}$。
由唯一分解定理,设 $n = \Pi_{i=1}^{n}p_{i}^{k_{i}}$,其中 $p_{i}$是质数,有 $\phi(n) = n \times \Pi_{i=1}^{s} \frac{p_{i} - 1}{p_{i}}$。
求欧拉函数值
1 | int euler_phi(int n) |
如果将上面的程序改成如下形式,会提升一点效率:
1 | int euler_phi(int n) |
欧拉定理
若 $gcd(a, \ m) \ = \ 1$ 则 $a^{\phi(m)} \equiv 1 (mod m)$
扩展欧拉定理
$$
a^{b} \equiv \left{
\begin{aligned}
a^{b \ mod \ \phi(p)} & , & gcd(a, \ p) \ = \ 1 \
a^{b} & , & gcd(a, \ p) \ \neq \ 1 & , \ b \ < \ \phi(p) \ (mod \ p)\
a^{b \ mod \ \phi(p)+\phi(p)} & , & gcd(a, \ b) \ \neq \ 1 & , \ b \ \geq \ \phi(p)
\end{aligned}
\right.
$$
筛法
素数筛法
如果我们想要知道小于等于 n 有多少个素数呢?
一个自然的想法是对于小于等于 n 的每个数进行一次质数检验。这种暴力的做法显然不能达到最优复杂度。
埃拉托斯特尼(Eratosthenes)筛法 · $O(n\log\log n)$
如果 x 是合数,那么 x 的倍数也一定是合数。利用这个结论,我们可以避免很多次不必要的检测。
如果我们从小到大考虑每个数,然后同时把当前这个数的所有(比自己大的)倍数记为合数,那么运行结束的时候没有被标记的数就是素数了。
1 | int Eratosthenes(int n) |
筛至平方根 && 只筛奇数
显然,要找到直到 n 为止的所有素数,仅对不超过 $\sqrt{n}$ 的素数进行筛选就足够了。
因为除 2 以外的偶数都是合数,所以我们可以直接跳过它们,只用关心奇数就好。
1 | int n; |
线性筛法
埃氏筛法仍有优化空间,它会将一个合数重复多次标记。有没有什么办法省掉无意义的步骤呢?答案是肯定的。
如果能让每个合数都只被标记一次,那么时间复杂度就可以降到 $O(n)$ 了
1 | void init() |
- pri[i]: 第 i 个素数
- phi[i]: 1 ~ i 中与 i 互质的数的个数
筛法求欧拉函数
1 | void phi_table(int n, int* phi) |
筛法求约数个数
d[i]: i 的约数个数
num[i]: i 的最小质因子出现次数
定理:若 $n \ = \ \prod_{i=1}^{m} p_{i}^{c_{i}}$ 则 $d_{i} \ = \ \prod_{i=1}^{m}c_{i} \ + \ 1$
1 | void pre() |
筛法求约数和
f[i]: i 的约数和
g[i]: i 的最小质因子的 $p \ + \ p^{1} \ + \ p^{2} \ + \ \cdots \ + \ p^{k}$
1 | void pre() |
乘法逆元
简介
如果一个线性同余方程 $ax \ \equiv \ 1 \ (mod \ b)$ ,则 x 称为 a mod b 的逆元,记作 $a^{-1}$。
如何求逆元
扩展欧几里得法
1 | void exgcd(int a, int b, int& x, int& y) |
快速幂法
因为 $ax \ \equiv \ 1 \ (mod \ b)$;
所以 $ax \ \equiv \ a^{b-1} \ (mod \ b)$(根据费马小定理);
所以 $x \ \equiv \ a^{b-2} \ (mod \ b)$。
然后我们就可以用快速幂来求了。
1 | inline int qpow(long long a, int b) |
- 使用费马小定理需要限制 b 是一个素数,而扩展欧几里得算法只要求 $\gcd(a, \ p) \ = \ 1$。
线性求逆元
1 | inv[1] = 1; |
线性求任意 n 个数的逆元
1 | s[0] = 1; |
中国剩余定理
简介
中国剩余定理 (Chinese Remainder Theorem, CRT) 可求解如下形式的一元线性同余方程组(其中 $n_{1}, \ n_{2}, \ \cdots, \ n_{k}$ 两两互质):
$$
\left {
\begin{array}{c}
x \ &\equiv \ &a_{1} \ (mod \ n_{1}) \
x \ &\equiv \ &a_{2} \ (mod \ n_{2}) \
&\cdots\ \
x \ &\equiv \ &a_{k} \ (mod \ n_{k})
\end{array}
\right .
$$
算法流程
- 计算所有模数的积 n;
- 对于第 i 个方程:
- 计算 $m_{i} \ = \ \frac{n}{n_{i}}$;
- 计算 $m_{i}$ 在模 $n_{i}$ 意义下的逆元 $m_{i}^{-1}$
- 计算 $c_{i} \ = \ m_{i}m_{i}^{-1}$(不要对 $n_{i}$ 取模)。
- 方程组的唯一解为:$a \ = \ \sum_{i=1}^{k}a_{i}c_{i} \ (mod \ n)$。
About this Post
This post is written by OwlllOvO, licensed under CC BY-NC 4.0.