数论
数论
参考资料:李煜东《算法竞赛进阶指南》0x30 数学知识
目录
0x31 质数
1. 基本概念
质数(素数):若一个大于 \(1\) 的正整数,无法被除了 \(1\) 和它本身以外的任何正整数整除,则称该数为质数。否则称为合数。
性质:
- \(1\) 既不是质数也不是合数。
- 质数有无穷多个。
素数定理(Prime Number Theorem)
素数分布理论的中心定理。设 \(x \ge 1\),以 \(\pi(x)\) 表示不超过 \(x\) 的素数的个数,当 \(x \to \infty\) 时:
也就是说,不超过 \(N\) 的质数约有 \(\dfrac{N}{\ln N}\) 个,平均每隔 \(\ln N\) 个数出现一个质数。从不大于 \(n\) 的自然数中随机选一个,它是素数的概率大约是 \(\dfrac{1}{\ln n}\)。
各范围内质数个数参考:
| 范围 | 质数个数 |
|---|---|
| \(10^1\) 以内 | 4 |
| \(10^2\) 以内 | 24 |
| \(10^3\) 以内 | 168 |
| \(10^4\) 以内 | 1228 |
| \(10^5\) 以内 | 9591 |
| \(10^6\) 以内 | 78498 |
| \(10^7\) 以内 | 664578 |
| \(10^8\) 以内 | 5761455 |
2. 算术基本定理
任何一个大于等于 \(2\) 的自然数 \(N\) 都可以唯一分解成有限个素数(质数)的幂的乘积:
这里 \(p_1 < p_2 < p_3 < \dots < p_r\) 均为素数,其中指数 \(c_i\) 是正整数。该式称为 \(N\) 的标准分解式。
重要结论:前 \(16\) 个质数的乘积约为 \(3.3 \times 10^{19}\),已经超过 long long 的最大值(约 \(9.2 \times 10^{18}\)),故对整数进行质因数分解的时候,质因子的个数不会太多(最多 \(15\) 个左右)。
试除法求标准分解式
从小到大枚举每个数 \(d\),若 \(d\) 整除 \(n\),则不断除以 \(d\) 并计数。
// 试除法求整数 n 的标准分解式
// p[] 存质因子,c[] 存对应指数,m 为质因子个数
int p[20], c[20], m;
void divide(int n) {
m = 0;
for (int i = 2; i <= n / i; i++) {
if (n % i == 0) {
p[++m] = i, c[m] = 0;
while (n % i == 0) {
n /= i;
c[m]++;
}
}
}
if (n > 1) { // 剩下的 n 本身是质数
p[++m] = n, c[m] = 1;
}
}
时间复杂度:\(O(\sqrt{n})\)
注意:循环条件写
i <= n / i而不是i * i <= n,避免i*i溢出 int。
3. 阶乘分解
求 \(n!\) 的标准分解式中每个质因数 \(p\) 的指数。
推导
在 \(1 \sim n\) 中:
- 是 \(p\) 的倍数的数有 \(p, 2p, 3p, \cdots\),共 \(\left\lfloor \dfrac{n}{p} \right\rfloor\) 个。
- 是 \(p^2\) 的倍数的数有 \(p^2, 2p^2, 3p^2, \cdots\),共 \(\left\lfloor \dfrac{n}{p^2} \right\rfloor\) 个。
- \(\cdots\)
考虑各数的贡献:
- 仅仅是 \(p\) 的倍数(不是 \(p^2, p^3, \dots\) 的倍数)的数有 \(\left\lfloor \dfrac{n}{p} \right\rfloor - \left\lfloor \dfrac{n}{p^2} \right\rfloor\) 个,贡献均为 \(1\)。
- 仅仅是 \(p^2\) 的倍数(不是 \(p^3, p^4, \dots\) 的倍数)的数有 \(\left\lfloor \dfrac{n}{p^2} \right\rfloor - \left\lfloor \dfrac{n}{p^3} \right\rfloor\) 个,贡献均为 \(2\)。
- 以此类推。
故最终结果为:
代码实现
// 求 n! 中质因子 p 的指数
int legendre(int n, int p) {
int c = 0;
while (n) {
c += n / p;
n /= p;
}
return c;
}
完整实现:求 n! 的标准分解式
先用线性筛求出 \(1 \sim n\) 的所有质数,再对每个质数计算其在 \(n!\) 中的指数。
#include <iostream>
using namespace std;
const int N = 1e6 + 10;
int v[N], prime[N], cnt;
int C[N]; // C[i] 表示 prime[i] 在 n! 中的指数
void Euler(int n) {
for (int i = 2; i <= n; i++) {
if (!v[i]) v[i] = i, prime[++cnt] = i;
for (int j = 1; j <= cnt; j++) {
if (prime[j] > n / i) break;
v[i * prime[j]] = prime[j];
if (i % prime[j] == 0) break;
}
}
}
int main() {
int n;
cin >> n;
Euler(n);
for (int i = 1; i <= cnt; i++) {
int t = n;
while (t) {
C[i] += t / prime[i];
t /= prime[i];
}
}
for (int i = 1; i <= cnt; i++)
cout << prime[i] << " " << C[i] << endl;
}
时间复杂度
\(1 \sim N\) 中质数的个数为 \(O\left(\dfrac{N}{\ln N}\right)\) 个,对于每个质数 \(p\),需要 \(O(\log_p N)\) 的时间计算指数,故对整个 \(N!\) 分解质因数的总时间复杂度为 \(O(N)\)。
4. 质数的判定
性质
若一个正整数 \(n\) 为合数,则该数存在小于等于 \(\sqrt{n}\) 的因数。
证明(反证法):假设 \(n\) 的所有约数都大于 \(\sqrt{n}\),任取两个约数 \(a, b\),则 \(a \cdot b > \sqrt{n} \cdot \sqrt{n} = n\),与 \(a \cdot b = n\) 矛盾。
方法一:试除法
以 \(2\) 到 \(\sqrt{n}\) 的整数挨着试除 \(n\),若 \(n\) 被整除,则说明 \(n\) 是合数。
bool is_prime(int n) {
if (n < 2) return false;
for (int i = 2; i <= n / i; i++)
if (n % i == 0) return false;
return true;
}
时间复杂度:\(O(\sqrt{n})\)
5. 质数的筛选
方法一:埃氏筛法(Eratosthenes)
思想:从 \(2\) 开始,由小到大扫描每个数 \(x\),把它的倍数 \(2x, 3x, \dots\) 标记为合数。当扫描到一个数时,若它未被标记,则说明它不存在大于 \(1\) 小于它本身的因数,所以该数是质数。
优化:注意到小于 \(x^2\) 的 \(x\) 的倍数在扫描比 \(x\) 更小的数的时候就已经被标记过了,所以对每个数 \(x\),可以从 \(x^2\) 开始标记。
bool vis[N];
int prime[N], cnt;
void primes(int n) {
for (int x = 2; x <= n; x++) {
if (vis[x]) continue;
prime[++cnt] = x;
for (int k = x; k <= n / x; k++)
vis[k * x] = 1;
}
}
时间复杂度:\(O(n \log \log n)\),接近 \(O(n)\)。
缺点:一个合数可能被多个质因子重复标记(如 \(12 = 2 \times 6 = 3 \times 4\),会被 2 和 3 各标记一次)。
方法二:欧拉筛法(线性筛)
核心思想:保证每个合数只会被其最小的质因数筛掉,从而在线性时间得到 \(1 \sim N\) 的质数。
手动模拟
- \(i = 2\):2 未被标记,加入质数表
[2]。用 \(2 \times 2 = 4\),标记 4 为合数。 - \(i = 3\):3 未被标记,加入质数表
[2, 3]。用 \(3 \times 2 = 6\)、\(3 \times 3 = 9\),均标记为合数。 - \(i = 4\):4 已被标记。用 \(4 \times 2 = 8\),标记 8。检测到 \(2 \mid 4\),退出。(\(4 \times 3 = 12\) 应该由 2 来筛,而不是 3)
- \(i = 5\):5 未被标记,加入质数表
[2, 3, 5]。用 \(5 \times 2 = 10\)、\(5 \times 3 = 15\)、\(5 \times 5 = 25\),均标记为合数。 - \(\cdots\)
- \(i = 35\):现质数表
[2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31]。用 \(35 \times 2 = 70\)、\(35 \times 3 = 105\)、\(35 \times 5 = 175\)。检测到 \(5 \mid 35\),退出。(\(35 \times 7 = 245\) 应该由 5 来筛,而不是 7)
代码实现
int v[N]; // v[i] 保存 i 的最小质因子
int prime[N]; // prime[] 保存质数表
int cnt; // 质数个数
void Euler(int n) {
for (int i = 2; i <= n; i++) {
if (!v[i]) {
v[i] = i;
prime[++cnt] = i;
}
for (int j = 1; j <= cnt; j++) {
if (prime[j] > n / i) break;
v[i * prime[j]] = prime[j];
if (i % prime[j] == 0) break; // 关键:prime[j] 是 i 的最小质因子
}
}
}
时间复杂度:\(O(n)\)
关键理解:if (i % prime[j] == 0) break; 保证了每个合数只会被它的最小质因子筛掉一次。
6. 区间筛法(大区间质数)
问题
求区间 \([L, R]\) 中的所有质数,其中 \(1 \le L < R \le 2^{31} - 1\),但 \(R - L \le 10^6\)。
思路
\([1, \sqrt{R}]\) 中的质数足以筛掉 \([L, R]\) 中的所有合数。
- 先筛出 \([1, \sqrt{R}]\) 中的所有质数。
- 用这些质数去标记区间 \([L, R]\) 中的合数。
- 区间内未被标记的就是质数。
// 核心思想:用 [1, sqrt(R)] 的质数去筛 [L, R]
// 标记数组下标偏移 L,即 vis[i] 对应数 L+i
时间复杂度:\(O(\sqrt{R} + (R-L)\log\log R)\)
7. 总结
| 场景 | 推荐方法 | 时间复杂度 |
|---|---|---|
| 判断单个数是否为质数 | 试除法 | \(O(\sqrt{n})\) |
| 求 \(1 \sim n\) 的所有质数 | 欧拉筛(线性筛) | \(O(n)\) |
| 求大区间 \([L, R]\) 的质数 | 区间筛法(二次筛) | \(O(\sqrt{R} + (R-L)\log\log R)\) |
| 单个整数质因数分解 | 试除法 | \(O(\sqrt{n})\) |
| \(n!\) 质因数分解 | 线性筛 + Legendre 公式 | \(O(n)\) |
0x32 约数
1. 基本概念
约数(因数):若整数 \(n\) 除以整数 \(d\) 的余数为 \(0\),即 \(d \mid n\),则称 \(d\) 是 \(n\) 的约数。
性质:
- \(1\) 和 \(n\) 都是 \(n\) 的约数。
- 若 \(d\) 是 \(n\) 的约数,则 \(n/d\) 也是 \(n\) 的约数(约数成对出现)。
- \(n\) 的约数个数约为 \(O(\sqrt{n})\) 级别,实际中非常少。
2. 求约数
试除法求所有约数
枚举 \(1\) 到 \(\sqrt{n}\),若 \(i \mid n\),则 \(i\) 和 \(n/i\) 都是约数。
vector<int> get_divisors(int n) {
vector<int> res;
for (int i = 1; i <= n / i; i++) {
if (n % i == 0) {
res.push_back(i);
if (i != n / i) res.push_back(n / i);
}
}
sort(res.begin(), res.end());
return res;
}
时间复杂度:\(O(\sqrt{n})\)
倍数法求 1~n 每个数的约数集合
反过来,对于每个 \(d\),枚举它的倍数,把 \(d\) 加入倍数的约数集合中。
vector<int> divs[N];
void get_all_divisors(int n) {
for (int i = 1; i <= n; i++) {
for (int j = i; j <= n; j += i) {
divs[j].push_back(i);
}
}
}
时间复杂度:\(O(n \log n)\)(调和级数)
3. 约数个数与约数和
由算术基本定理,设 \(N = p_1^{c_1} \cdot p_2^{c_2} \cdots p_m^{c_m}\),则:
约数个数
理解:每个质因子 \(p_i\) 可以选 \(0 \sim c_i\) 个,共 \(c_i + 1\) 种选择。
约数和
理解:乘法分配律展开后就是所有约数的和。
4. 最大公约数(GCD)
定义
若 \(d\) 同时整除 \(a\) 和 \(b\),则称 \(d\) 是 \(a, b\) 的公约数。所有公约数中最大的称为最大公约数,记为 \(\gcd(a, b)\)。
欧几里得算法(辗转相除法)
证明:
设 \(a = qb + r\),其中 \(0 \le r < b\),即 \(r = a \bmod b\)。
- 对任意公约数 \(d\),\(d \mid a\) 且 \(d \mid b\),则 \(d \mid (a - qb) = r\),即 \(d\) 也是 \(b\) 和 \(r\) 的公约数。
- 反之,若 \(d \mid b\) 且 \(d \mid r\),则 \(d \mid (qb + r) = a\),即 \(d\) 也是 \(a\) 和 \(b\) 的公约数。
- 因此两数的公约数集合完全相同,最大公约数自然相同。
ll gcd(ll x, ll y) {
if (y) return gcd(y, x % y);
else return x;
}
时间复杂度:\(O(\log \min(a, b))\)
最小公倍数(LCM)
注意:计算时先除后乘,避免溢出:
a / gcd(a, b) * b
5. 更相减损术(二进制优化)
对于大整数(高精度),取模运算较慢,可以用更相减损术:
优化(Stein 算法):
- 若 \(a, b\) 均为偶数:\(\gcd(a, b) = 2 \cdot \gcd(a/2, b/2)\)
- 若 \(a\) 偶 \(b\) 奇:\(\gcd(a, b) = \gcd(a/2, b)\)
- 若 \(a, b\) 均为奇数:\(\gcd(a, b) = \gcd(b, a-b)\)(\(a-b\) 为偶数)
用位运算加速,适用于高精度 gcd。
6. 欧拉函数
定义
\([1, N]\) 中与 \(N\) 互质的数的个数称为欧拉函数,记为 \(\varphi(N)\)。
计算公式
由算术基本定理 \(N = p_1^{c_1} p_2^{c_2} \cdots p_m^{c_m}\),则:
理解(容斥原理):从 \(1 \sim N\) 中去掉所有 \(p_i\) 的倍数,加回同时是 \(p_i p_j\) 倍数的,……
性质
- 若 \(n\) 是质数,则 \(\varphi(n) = n - 1\)。
- 若 \(p\) 是质数,\(n = p^k\),则 \(\varphi(n) = p^k - p^{k-1} = (p-1) \cdot p^{k-1}\)。
- 欧拉函数是积性函数:若 \(\gcd(a, b) = 1\),则 \(\varphi(ab) = \varphi(a) \cdot \varphi(b)\)。
- \(\sum_{d \mid n} \varphi(d) = n\)(所有约数的欧拉函数之和等于 \(n\))。
求单个欧拉函数
int phi(int n) {
int res = n;
for (int i = 2; i <= n / i; i++) {
if (n % i == 0) {
res = res / i * (i - 1);
while (n % i == 0) n /= i;
}
}
if (n > 1) res = res / n * (n - 1);
return res;
}
筛法求欧拉函数(线性筛)
在线性筛的同时计算每个数的欧拉函数:
int primes[N], cnt;
int phi[N];
bool v[N];
void get_euler(int n) {
phi[1] = 1;
for (int i = 2; i <= n; i++) {
if (!v[i]) {
primes[cnt++] = i;
phi[i] = i - 1; // 质数的欧拉函数 = i-1
}
for (int j = 0; primes[j] <= n / i; j++) {
v[primes[j] * i] = true;
if (i % primes[j] == 0) {
// primes[j] 是 i 的最小质因子
// phi(i * primes[j]) = phi(i) * primes[j]
phi[i * primes[j]] = phi[i] * primes[j];
break;
}
// primes[j] 与 i 互质
// phi(i * primes[j]) = phi(i) * (primes[j] - 1)
phi[i * primes[j]] = phi[i] * (primes[j] - 1);
}
}
}
核心推导:
- 若 \(p \mid i\)(\(p\) 是质数),则 \(i \times p\) 的质因子集合与 \(i\) 相同,故 \(\varphi(ip) = \varphi(i) \cdot p\)。
- 若 \(p \nmid i\),则 \(p\) 与 \(i\) 互质,由积性 \(\varphi(ip) = \varphi(i) \cdot (p-1)\)。
0x33 同余
1. 基本概念
同余
若整数 \(a, b\) 除以正整数 \(m\) 的余数相同,则称 \(a, b\) 模 \(m\) 同余,记为:
性质
- 自反性:\(a \equiv a \pmod m\)
- 对称性:\(a \equiv b \Rightarrow b \equiv a \pmod m\)
- 传递性:\(a \equiv b, b \equiv c \Rightarrow a \equiv c \pmod m\)
- 加法:\(a \equiv b, c \equiv d \Rightarrow a + c \equiv b + d \pmod m\)
- 乘法:\(a \equiv b, c \equiv d \Rightarrow ac \equiv bd \pmod m\)
- 幂运算:\(a \equiv b \Rightarrow a^n \equiv b^n \pmod m\)
注意:同余式不能直接两边"除以"一个数,需要乘逆元。
2. 扩展欧几里得算法
Bezout 定理
对于任意整数 \(a, b\),存在整数 \(x, y\),使得:
扩展欧几里得算法
求一组整数 \(x, y\),满足 \(ax + by = \gcd(a, b)\)。
推导:
设 \(d = \gcd(a, b)\),由欧几里得算法:\(d = \gcd(b, a \bmod b)\)。
假设已求得 \(bx' + (a \bmod b)y' = d\),而 \(a \bmod b = a - \lfloor a/b \rfloor \cdot b\),代入得:
因此 \(x = y'\),\(y = x' - \lfloor a/b \rfloor \cdot y'\)。
int exgcd(int a, int b, int &x, int &y) {
if (b == 0) {
x = 1, y = 0;
return a;
}
int d = exgcd(b, a % b, y, x);
y -= a / b * x;
return d;
}
返回值:\(\gcd(a, b)\),同时 \(x, y\) 被赋值为一组解。
通解
若 \(ax + by = d\) 的一组特解为 \((x_0, y_0)\),则通解为:
3. 线性同余方程
定义
形如 \(ax \equiv b \pmod m\) 的方程称为线性同余方程。
求解方法
方程等价于 \(ax - b = km\),即 \(ax + m(-k) = b\),也就是求 \(ax + my = b\) 的整数解。
由 Bezout 定理,方程有解当且仅当 \(\gcd(a, m) \mid b\)。
步骤:
- 用扩展欧几里得求 \(ax_0 + my_0 = d\),其中 \(d = \gcd(a, m)\)。
- 若 \(d \nmid b\),无解。
- 否则,\(x = x_0 \cdot \frac{b}{d} \bmod m\) 是一个特解。
- 通解:\(x \equiv x_0 \cdot \frac{b}{d} \pmod{\frac{m}{d}}\)。
// 求 ax ≡ b (mod m) 的最小正整数解
int mod_equation(int a, int b, int m) {
int x, y;
int d = exgcd(a, m, x, y);
if (b % d != 0) return -1; // 无解
x = (ll)x * (b / d) % m;
int mod = m / d;
x = (x % mod + mod) % mod; // 最小正整数解
return x;
}
4. 乘法逆元
定义
若整数 \(a, m\) 互质,存在整数 \(x\) 使得 \(ax \equiv 1 \pmod m\),则称 \(x\) 是 \(a\) 在模 \(m\) 意义下的乘法逆元,记为 \(a^{-1}\)。
存在条件:\(\gcd(a, m) = 1\)。
求法一:扩展欧几里得
求 \(ax \equiv 1 \pmod m\),即 \(ax + my = 1\)。因为 \(a, m\) 互质,\(\gcd(a, m) = 1\),直接用扩展欧几里得。
int inv(int a, int m) {
int x, y;
exgcd(a, m, x, y);
return (x % m + m) % m;
}
求法二:费马小定理
若 \(p\) 是质数,且 \(a\) 不是 \(p\) 的倍数,则:
因此 \(a \cdot a^{p-2} \equiv 1 \pmod p\),即 \(a^{-1} \equiv a^{p-2} \pmod p\)。
用快速幂计算。
int fpm(int a, int b, int p) {
int res = 1 % p;
while (b) {
if (b & 1) res = (ll)res * a % p;
a = (ll)a * a % p;
b >>= 1;
}
return res;
}
// a 在模 p(质数)下的逆元
int inv(int a, int p) {
return fpm(a, p - 2, p);
}
求法三:线性递推求逆元(\(O(n)\))
求 \(1 \sim n\) 所有数在模质数 \(p\) 下的逆元:
int inv[N];
void get_inv(int n, int p) {
inv[1] = 1;
for (int i = 2; i <= n; i++) {
inv[i] = (ll)(p - p / i) * inv[p % i] % p;
}
}
5. 中国剩余定理(CRT)
问题
求解同余方程组:
其中 \(m_1, m_2, \dots, m_n\) 两两互质。
解法
设 \(M = \prod m_i\),\(M_i = M / m_i\),\(t_i = M_i^{-1} \pmod {m_i}\),则解为:
理解:每一项 \(a_i M_i t_i\) 满足:
- 模 \(m_i\) 时,\(M_i t_i \equiv 1\),故该项 \(\equiv a_i \pmod {m_i}\)
- 模其他 \(m_j\) 时,\(M_i\) 包含因子 \(m_j\),故该项 \(\equiv 0 \pmod {m_j}\)
int crt(int n, int a[], int m[]) {
int M = 1;
for (int i = 1; i <= n; i++) M *= m[i];
int res = 0;
for (int i = 1; i <= n; i++) {
int Mi = M / m[i];
int ti = inv(Mi, m[i]); // Mi 在模 m[i] 下的逆元
res = (res + (ll)a[i] * Mi % M * ti % M) % M;
}
return (res % M + M) % M;
}
扩展中国剩余定理(模数不互质)
当模数不两两互质时,需要逐组合并:
对于两个方程 \(x \equiv a_1 \pmod {m_1}\) 和 \(x \equiv a_2 \pmod {m_2}\):
- \(x = a_1 + k \cdot m_1\),代入第二个方程:\(a_1 + k \cdot m_1 \equiv a_2 \pmod {m_2}\)
- 即 \(k \cdot m_1 \equiv a_2 - a_1 \pmod {m_2}\)
- 用扩展欧几里得求解 \(k\),得到新的方程 \(x \equiv a' \pmod {\operatorname{lcm}(m_1, m_2)}\)
依次合并所有方程即可。
6. 欧拉定理与费马小定理
欧拉定理
若 \(\gcd(a, m) = 1\),则:
费马小定理(欧拉定理的特例)
若 \(p\) 是质数且 \(p \nmid a\),则:
欧拉定理的推论
若 \(\gcd(a, m) = 1\),则 \(a^b \equiv a^{b \bmod \varphi(m)} \pmod m\)。
更一般地(扩展欧拉定理):
(不需要 \(a, m\) 互质)
0x34 组合计数与容斥
1. 组合数
定义
从 \(n\) 个不同元素中取出 \(m\) 个的方案数:
性质
- \(\binom{n}{m} = \binom{n}{n-m}\)
- \(\binom{n}{m} = \binom{n-1}{m} + \binom{n-1}{m-1}\)(杨辉三角递推)
- \(\sum_{i=0}^{n} \binom{n}{i} = 2^n\)
求法
- 递推(杨辉三角):\(O(n^2)\),适用于 \(n\) 较小。
- 预处理阶乘和逆元:\(O(n)\) 预处理,\(O(1)\) 查询,适用于模数为质数。
- Lucas 定理:当 \(n, m\) 很大而模数 \(p\) 较小时使用。
// 预处理阶乘和逆元(模数 p 为质数)
int fact[N], inv_fact[N];
void init(int n, int p) {
fact[0] = 1;
for (int i = 1; i <= n; i++)
fact[i] = (ll)fact[i-1] * i % p;
inv_fact[n] = fpm(fact[n], p - 2, p);
for (int i = n - 1; i >= 0; i--)
inv_fact[i] = (ll)inv_fact[i+1] * (i+1) % p;
}
int C(int n, int m, int p) {
if (m < 0 || m > n) return 0;
return (ll)fact[n] * inv_fact[m] % p * inv_fact[n-m] % p;
}
Lucas 定理
若 \(p\) 是质数,则:
int lucas(ll n, ll m, int p) {
if (m == 0) return 1;
return (ll)C(n % p, m % p, p) * lucas(n / p, m / p, p) % p;
}
2. 容斥原理
基本形式
设 \(S_1, S_2, \dots, S_n\) 是有限集合,则:
口诀:奇加偶减(元素个数为奇数的交集加,偶数的减)。
经典应用
求 \([1, n]\) 中与 \(m\) 互质的数的个数:
- 设 \(m = p_1 p_2 \dots p_k\)(不同质因子)
- \(S_i\) 表示 \([1, n]\) 中 \(p_i\) 的倍数集合
- 答案 \(= n - \bigcup |S_i| = n - \sum \lfloor n/p_i \rfloor + \sum \lfloor n/(p_i p_j) \rfloor - \dots\)
这正是欧拉函数的公式来源。
3. 莫比乌斯函数
定义
性质
- 莫比乌斯函数是积性函数。
- \(\sum_{d \mid n} \mu(d) = [n = 1]\)(即 \(n=1\) 时为 \(1\),否则为 \(0\))。这是最常用的性质。
线性筛求莫比乌斯函数
int primes[N], cnt;
int mu[N];
bool v[N];
void get_mobius(int n) {
mu[1] = 1;
for (int i = 2; i <= n; i++) {
if (!v[i]) {
primes[cnt++] = i;
mu[i] = -1;
}
for (int j = 0; primes[j] <= n / i; j++) {
v[primes[j] * i] = true;
if (i % primes[j] == 0) {
mu[i * primes[j]] = 0; // 有平方因子
break;
}
mu[i * primes[j]] = -mu[i];
}
}
}
常用模板汇总
快速幂
ll qpow(ll a, ll b, ll p) {
ll res = 1 % p;
while (b) {
if (b & 1) res = res * a % p;
a = a * a % p;
b >>= 1;
}
return res;
}
扩展欧几里得
ll exgcd(ll a, ll b, ll &x, ll &y) {
if (b == 0) { x = 1; y = 0; return a; }
ll d = exgcd(b, a % b, y, x);
y -= a / b * x;
return d;
}
线性筛质数
int primes[N], cnt;
bool v[N];
void sieve(int n) {
for (int i = 2; i <= n; i++) {
if (!v[i]) primes[cnt++] = i;
for (int j = 0; primes[j] <= n / i; j++) {
v[primes[j] * i] = true;
if (i % primes[j] == 0) break;
}
}
}
线性筛欧拉函数
int phi[N];
void sieve_phi(int n) {
phi[1] = 1;
for (int i = 2; i <= n; i++) {
if (!v[i]) { primes[cnt++] = i; phi[i] = i - 1; }
for (int j = 0; primes[j] <= n / i; j++) {
v[primes[j] * i] = true;
if (i % primes[j] == 0) {
phi[i * primes[j]] = phi[i] * primes[j];
break;
}
phi[i * primes[j]] = phi[i] * (primes[j] - 1);
}
}
}
线性筛莫比乌斯函数
int mu[N];
void sieve_mu(int n) {
mu[1] = 1;
for (int i = 2; i <= n; i++) {
if (!v[i]) { primes[cnt++] = i; mu[i] = -1; }
for (int j = 0; primes[j] <= n / i; j++) {
v[primes[j] * i] = true;
if (i % primes[j] == 0) {
mu[i * primes[j]] = 0;
break;
}
mu[i * primes[j]] = -mu[i];
}
}
}
质因数分解
void divide(int n, int p[], int c[], int &m) {
m = 0;
for (int i = 2; i <= n / i; i++) {
if (n % i == 0) {
p[++m] = i, c[m] = 0;
while (n % i == 0) n /= i, c[m]++;
}
}
if (n > 1) p[++m] = n, c[m] = 1;
}
阶乘分解(Legendre 公式)
int legendre(int n, int p) {
int c = 0;
while (n) {
c += n / p;
n /= p;
}
return c;
}
复杂度总结表
| 算法 | 时间复杂度 | 说明 |
|---|---|---|
| 试除法判定质数 | \(O(\sqrt{n})\) | 可加 6k±1 优化,随机数据快 3~4 倍 |
| 埃氏筛 | \(O(n \log \log n)\) | 简单好写 |
| 线性筛(欧拉筛) | \(O(n)\) | 可同时筛欧拉函数、莫比乌斯函数 |
| 试除法分解质因数 | \(O(\sqrt{n})\) | 质因子个数不超过 ~15 个 |
| 阶乘分解 | \(O(n)\) | 线性筛 + Legendre 公式 |
| 欧几里得算法 | \(O(\log n)\) | |
| 扩展欧几里得 | \(O(\log n)\) | 求逆元、解线性同余方程 |
| 快速幂 | \(O(\log b)\) | |
| 中国剩余定理 | \(O(n \log m)\) | 模数互质 |
本笔记基于李煜东《算法竞赛进阶指南》数论相关章节整理,部分由 AI 润色
原创·转载请注明出处
✦
浙公网安备 33010602011771号