素性判定:
// 判断一个数是否为素数
bool is_prime(int x) {
// 特判 1 的情况
if (x == 1) return false;
// 从 2 枚举到 √x,判断是否能整除
for (int i = 2; i * i <= x; ++i) {
// 如果能整除,则为合数
if (x % i == 0) return false;
}
// 如果都不能整除,则为素数
return true;
}
朴素筛法:
int vis[MAXN], p[MAXN], cnt;
// 筛出 [2, n] 之间所有的素数
// 复杂度:O(n log n)
void naive_sieve(int n) {
for (int i = 2; i <= n; ++i) {
if (!vis[i]) p[cnt++] = i;
// 筛掉 i 除了本身以外的所有倍数
for (int j = i + i; j <= n; j += i) {
vis[j] = 1;
}
}
}
埃式筛法:
int vis[MAXN], p[MAXN], cnt;
// 筛出 [2, n] 之间所有的素数
// 复杂度:O(n log log n)
void eratosthenes_sieve(int n) {
for (int i = 2; i <= n; ++i) {
if (!vis[i]) {
p[cnt++] = i;
// 筛掉 i 除了本身以外的所有倍数
for (int j = i + i; j <= n; j += i) {
vis[j] = 1;
}
}
}
}
欧拉筛法(线性筛):
int vis[MAXN], p[MAXN], cnt;
// 筛出 [2, n] 之间所有的素数
// 复杂度:O(n)
void euler_sieve(int n) {
for (int i = 2; i <= n; ++i) {
if (!vis[i]) p[cnt++] = i;
for (int j = 0; i * p[j] <= n; ++j) {
vis[i * p[j]] = 1;
// 保证 p[j] 是 i*p[j] 最小的素因子
if (i % p[j] == 0) break;
}
}
}
唯一分解定理(算术基本定理)
(唯一分解定理) 每个大于 1 的自然数均可写为素数的积,而且这些素因子按大小 排列之后,写法仅有一种方式。
105 = 3 × 5 × 7
200 = 2 × 2 × 2 × 5 × 5 = 23 × 5 2
写成素因子的幂的积,x = p1 k1 p2 k2 p3 k3 · · · pn kn
如果不含某个素因子,可以看作它的次数为 0
乘法就是对应素因子的次数相加,除法就是对应素因子的次 数相减
如果一个数所有的素因子次数都大于等于另一个数对应的素 因子次数,那么就可以被它整除
素因数分解
// 将 x 分解素因数,结果从小到大储存在 p[] 中,返回素因子的个数
int factorize(int x, int p[]) {
int cnt = 0;
// 从 2 枚举到 √x,判断是否为约数
for (int i = 2; i * i <= x; ++i) {
// 如果找到一个约数,就不断从 x 中除去
while (x % i == 0) {
p[cnt++] = i;
x /= i;
}
}
// 如果 x > 1,则说明剩下的 x 是素数,也要放进数组
if (x > 1) p[cnt++] = x;
return cnt;
}
最大公约数 (gcd)
定义 (最大公约数) 两个自然数所共有的约数中最大的一个,称为它们的最大公约数 (Greatest Common Divisor)
利用唯一分解定理及推论,可以发现 gcd 就是两个数对应的 素因子次数取 min 后得到的数
实际应用中,通常使用辗转相除法来计算 gcd
辗转相除法(欧几里得算法)
引理 (辗转相除原理)
定义对于任意自然数 a,gcd(0, a) = gcd(a, 0) = a。则对于任意 自然数 a, b,满足 gcd(a, b) = gcd(a, b mod a)
利用这个性质,可以将 a, b 两者中较大的一个不断缩小,直 到变为零
每次大的数对小的数取模,至少缩小一半,所以复杂度为 O(log max(a, b))
辗转相除法
// 循环实现
int gcd(int a, int b) {
while (a>0) {
int t=b% a;
b= a;
a= t;
}
return b;
}
// 递归实现
int gcd(int a, int b) {
if (a ==0) return b;
return gcd(b% a, a);
}
最小公倍数 (lcm)
定义 (最小公倍数) 两个自然数所共有的倍数中最小的一个,称为它们的最小公倍数 (Least Common Multiple)
利用唯一分解定理及推论,可以发现 lcm 就是两个数对应 的素因子次数取 max 后得到的数
不需要另外去求,只要用一个公式就能转化成 gcd 问题
lcm(a, b) = ab gcd(a, b)
可以用唯一分解定理来证明
扩展欧几里得算法
定理 (裴蜀定理)
对于任意整数 a, b,存在无穷多组整数对 (x, y) 满足不定方程 ax + by = d,其中 d = gcd(a, b)
在求 gcd(a, b) 的同时,可以求出(关于 x, y 的)不定方程 ax + by = d 的一组整数解
考虑递归计算:假设已经算出了 (b%a, a) 的一组解 (x0, y0) 满足 (b%a)x0 + ay0 = d
可以得到 (b − a ⌊ b a ⌋ )x0 + ay0 = d • 整理得到 a(y0 − ⌊ b a ⌋ x0) + bx0 = d
为了方便,交换 x0 和 y0,得到 a(x0 − ⌊ b a ⌋ y0) + by0 = d
扩展欧几里得算法
// 返回 gcd(a, b) 和方程 ax + by = gcd(a, b) 的一组解
int gcd(int a, int b, int &x, int &y) {
if (a == 0) {
// gcd(0, b) = b,显然 0 · 0 + 1 · b = b 满足条件
x = 0, y = 1;
return b;
}
// 这里交换了 x0 和 y0
int d = gcd(b % a, a, y, x);
// x = x_0 - b / a \times y_0
// y = y_0
x -= b / a * y;
return d;
}
取模运算
对于自然数 a,正整数 m,a mod m = a − m ⌊ a m ⌋也称取余,因为实际上就是整除得到的余数a mod m ∈ [0, m)
取模运算和除法一样慢
尽量不要负数取模
模意义下的数和运算
如果 a mod m = b mod m,可以记做 a ≡ b (mod m)
所有自然数都能用 [0, m) 之间的整数来“代表”
可以把取模推广到负数,a mod m = b(b ∈ [0, m)) ⇔ 存在整 数 k,满足 a = km + b
我们可以建立一个新的数字运算体系,其中只有 0 ∼ m − 1 这 m 个数,然后建立四则运算等基本规则
两个数相加,如果超出了 m − 1,就从 0 开始再往上加,相 乘与相加类似
两个数相减,如果小于 0,就从 m − 1 再往下减
32 位无符号整数(unsigned int)的运算实际上是对 2 32 取 模的
运算的性质
(a + b)%m = (a%m + b%m)%m
(a − b)%m = (a%m − b%m)%m
(a × b)%m = (a%m) × (b%m)%m
假如题目要求最终答案对 m 取模,那么(为了防止数字过 大)可以在每一步运算中都取模
乘法逆元
加法、减法、乘法都定义好了,除法怎么做呢?
可以不断将被除数加上 m,直到可以除尽,但是这样做效 率太低了
回想起以前学习分数时,除以一个数,可以转化成乘它的倒 数
类似地,我们可以试着为除数找到一个乘法逆元,即满足 xx−1 ≡ 1 (mod m) 的整数 x −1
当且仅当 m 为素数时,每个数都有唯一的乘法逆元,因此 大部分 OI 题中的模数都是素数
费马小定理
定理 (费马小定理) 对于任意质数 p 和正整数 a < p,有 a p−1 ≡ 1 (mod p) 证明.
易证,a, 2a, 3a, . . . ,(p − 1)a (mod p) 可以取遍 1 到 p − 1 之 间的所有数
于是 a · 2a · 3a · · ·(p − 1)a ≡ (p − 1)! (mod p)
由于 p 是素数,和 1 ∼ p − 1 之间的数都互素,所以等式两 边可以同时除以 (p − 1)!
就得到了 a p−1 ≡ 1 (mod p)
快速求逆元
有了费马小定理,就可以快速求出一个数的乘法逆元
由于 a p−2 · a ≡ a p−1 ≡ 1 (mod p),所以 a p−2%p 就是逆元
可以通过快速幂来计算,复杂度 O(log p)
另外,也可以用扩展欧几里得算法来求逆元,求出 ax + py = 1 的一组解,x%p 就是逆元
中国剩余定理 (CRT)
有若干个人列队,三个一排会多出两个人,五个一排多三个 人,七个一排多两个人,求人数至少是多少?
本质上是解一元线性同余方程组
x ≡ a1 (mod m1)
x ≡ a2 (mod m2)
. . .
x ≡ an (mod mn)
当 m 两两互素时,一定有解
我们来尝试构造出这个解
设 M = m1 × m2 × · · · × mn,Mi = M mi
Mi 只有在模 mi 的时候不为零,模其他的 m 都为零
由于我们想在模 mi 时得到 ai,所以需要乘上 aiti,其中 ti 是 Mi 在模 mi 意义下的乘法逆元
最终得到 a1t1M1 + a2t2M2 + · · · + antnMn,再对 M 取模, 就得到了最小的自然数解,加上 kM 就是通解