3468 字
17 分钟
数论算法详解:从CSP-S到IOI

数论算法详解:从CSP-S到IOI#

数论是算法竞赛中的重要分支,从CSP-S到IOI级别的比赛都经常出现数论相关题目。本文将系统地介绍数论算法的核心知识点,从基础到进阶,帮助读者建立完整的数论知识体系。

一、质数与筛法#

1.1 质数的定义与判断#

质数(素数)是指在大于1的自然数中,除了1和它本身以外不再有其他因数的数。

朴素判断法:时间复杂度 O(√n)

bool isPrime(int n) {
if (n < 2) return false;
for (int i = 2; i * i <= n; i++) {
if (n % i == 0) return false;
}
return true;
}

1.2 埃拉托斯特尼筛法(Eratosthenes筛)#

算法思想:从小到大枚举每个质数,标记其所有倍数为合数。

时间复杂度:O(n log log n)

const int MAXN = 1e6 + 5;
bool isPrime[MAXN];
vector<int> primes;
void sieveOfEratosthenes(int n) {
fill(isPrime, isPrime + n + 1, true);
isPrime[0] = isPrime[1] = false;
for (int i = 2; i <= n; i++) {
if (isPrime[i]) {
primes.push_back(i);
for (long long j = (long long)i * i; j <= n; j += i) {
isPrime[j] = false;
}
}
}
}

1.3 线性筛法(欧拉筛)#

算法思想:每个合数只被其最小质因子筛掉一次,确保线性时间复杂度。

时间复杂度:O(n)

const int MAXN = 1e7 + 5;
bool isPrime[MAXN];
int primes[MAXN], cnt = 0;
void linearSieve(int n) {
fill(isPrime, isPrime + n + 1, true);
isPrime[0] = isPrime[1] = false;
for (int i = 2; i <= n; i++) {
if (isPrime[i]) primes[cnt++] = i;
for (int j = 0; j < cnt && (long long)i * primes[j] <= n; j++) {
isPrime[i * primes[j]] = false;
if (i % primes[j] == 0) break; // 关键:保证每个数只被最小质因子筛掉
}
}
}

核心理解:当 i % primes[j] == 0 时,primes[j]i 的最小质因子,也是 i * primes[j] 的最小质因子,此时应该停止,避免重复筛选。

二、最大公约数与最小公倍数#

2.1 欧几里得算法(辗转相除法)#

定理:gcd(a, b) = gcd(b, a mod b)

证明:设 d = gcd(a, b),则 d|a 且 d|b。因为 a = bq + r (其中 r = a mod b),所以 d|r。反之,若 d|b 且 d|r,则 d|a。因此 gcd(a, b) = gcd(b, r)。

时间复杂度:O(log min(a, b))

int gcd(int a, int b) {
return b == 0 ? a : gcd(b, a % b);
}
// 最小公倍数
int lcm(int a, int b) {
return a / gcd(a, b) * b; // 先除后乘防止溢出
}

2.2 扩展欧几里得算法#

问题:求解方程 ax + by = gcd(a, b) 的整数解。

递归思路

  • 当 b = 0 时,gcd(a, 0) = a,此时 x = 1, y = 0
  • 否则求解 bx’ + (a mod b)y’ = gcd(b, a mod b)
  • 因为 a mod b = a - ⌊a/b⌋ × b,代入得:bx’ + (a - ⌊a/b⌋ × b)y’ = gcd(a, b)
  • 整理得:ay’ + b(x’ - ⌊a/b⌋ × y’) = gcd(a, b)
  • 因此 x = y’, y = x’ - ⌊a/b⌋ × 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, p) = 1,则 a 在模 p 意义下的逆元为 x (mod p),其中 x 满足 ax ≡ 1 (mod p)。

int modInverse(int a, int p) {
int x, y;
int d = exgcd(a, p, x, y);
if (d != 1) return -1; // 不存在逆元
return (x % p + p) % p;
}

三、快速幂与矩阵快速幂#

3.1 快速幂算法#

问题:计算 a^n mod p

算法思想:将指数 n 二进制分解,利用 a^n = a^(2^k1) × a^(2^k2) × … 的性质。

时间复杂度:O(log n)

long long quickPow(long long a, long long n, long long p) {
long long res = 1;
a %= p;
while (n > 0) {
if (n & 1) res = res * a % p;
a = a * a % p;
n >>= 1;
}
return res;
}

3.2 矩阵快速幂#

应用:加速递推关系的计算,如斐波那契数列。

struct Matrix {
long long a[2][2];
Matrix() { memset(a, 0, sizeof(a)); }
Matrix operator * (const Matrix &b) const {
Matrix c;
for (int i = 0; i < 2; i++) {
for (int j = 0; j < 2; j++) {
for (int k = 0; k < 2; k++) {
c.a[i][j] = (c.a[i][j] + a[i][k] * b.a[k][j]) % MOD;
}
}
}
return c;
}
};
Matrix matPow(Matrix a, long long n) {
Matrix res;
res.a[0][0] = res.a[1][1] = 1; // 单位矩阵
while (n > 0) {
if (n & 1) res = res * a;
a = a * a;
n >>= 1;
}
return res;
}
// 斐波那契数列第n项
long long fibonacci(long long n) {
if (n <= 2) return 1;
Matrix m;
m.a[0][0] = m.a[0][1] = m.a[1][0] = 1;
m.a[1][1] = 0;
Matrix res = matPow(m, n - 2);
return (res.a[0][0] + res.a[0][1]) % MOD;
}

四、模运算与逆元#

4.1 模运算的基本性质#

  • (a + b) mod p = ((a mod p) + (b mod p)) mod p
  • (a - b) mod p = ((a mod p) - (b mod p) + p) mod p
  • (a × b) mod p = ((a mod p) × (b mod p)) mod p
  • (a / b) mod p = (a × b^(-1)) mod p (b^(-1) 为 b 的逆元)

4.2 费马小定理#

定理:若 p 为质数,gcd(a, p) = 1,则 a^(p-1) ≡ 1 (mod p)

推论:a^(p-2) ≡ a^(-1) (mod p)

应用:当模数为质数时,快速求逆元

long long modInverse(long long a, long long p) {
return quickPow(a, p - 2, p);
}

4.3 线性求逆元#

问题:求 1, 2, …, n 在模 p 下的逆元

递推公式:inv[i] = (p - p/i) × inv[p % i] % p

证明:设 p = ki + r (r = p % i),则 ki + r ≡ 0 (mod p),两边同时乘以 i^(-1) × r^(-1),得 i^(-1) ≡ -k × r^(-1) (mod p)

long long inv[MAXN];
void calcInverse(int n, int p) {
inv[1] = 1;
for (int i = 2; i <= n; i++) {
inv[i] = (p - p / i) * inv[p % i] % p;
}
}

时间复杂度:O(n)

4.4 中国剩余定理(CRT)#

问题:求解同余方程组

x ≡ a1 (mod m1)
x ≡ a2 (mod m2)
...
x ≡ ak (mod mk)

其中 m1, m2, …, mk 两两互质。

算法

  1. 令 M = m1 × m2 × … × mk
  2. 令 Mi = M / mi
  3. 求 ti 满足 Mi × ti ≡ 1 (mod mi)
  4. 答案 x = Σ(ai × Mi × ti) mod M
long long CRT(int n, long long a[], long long m[]) {
long long M = 1, ans = 0;
for (int i = 0; i < n; i++) M *= m[i];
for (int i = 0; i < n; i++) {
long long Mi = M / m[i];
long long ti = modInverse(Mi, m[i]);
ans = (ans + a[i] * Mi % M * ti % M) % M;
}
return (ans % M + M) % M;
}

五、欧拉函数#

5.1 定义与性质#

定义:φ(n) 表示小于等于 n 且与 n 互质的正整数个数。

性质

  1. φ(1) = 1
  2. 若 p 为质数,φ(p) = p - 1
  3. 若 p 为质数,φ(p^k) = p^k - p^(k-1) = p^(k-1) × (p - 1)
  4. 若 gcd(m, n) = 1,φ(m × n) = φ(m) × φ(n)(积性函数)

5.2 单个数的欧拉函数#

公式:若 n = p1^k1 × p2^k2 × … × pm^km,则 φ(n) = n × (1 - 1/p1) × (1 - 1/p2) × … × (1 - 1/pm)

long long phi(long long n) {
long long res = n;
for (long long i = 2; i * i <= n; 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;
}

时间复杂度:O(√n)

5.3 线性筛求欧拉函数#

long long phi[MAXN];
int primes[MAXN], cnt = 0;
bool isPrime[MAXN];
void phiTable(int n) {
phi[1] = 1;
fill(isPrime, isPrime + n + 1, true);
for (int i = 2; i <= n; i++) {
if (isPrime[i]) {
primes[cnt++] = i;
phi[i] = i - 1;
}
for (int j = 0; j < cnt && (long long)i * primes[j] <= n; j++) {
isPrime[i * primes[j]] = false;
if (i % primes[j] == 0) {
phi[i * primes[j]] = phi[i] * primes[j];
break;
} else {
phi[i * primes[j]] = phi[i] * (primes[j] - 1);
}
}
}
}

5.4 欧拉定理#

定理:若 gcd(a, n) = 1,则 a^φ(n) ≡ 1 (mod n)

应用:降幂公式,a^b ≡ a^(b mod φ(n)) (mod n)(当 b ≥ φ(n) 时)

六、组合数学#

6.1 排列组合基础#

排列数:A(n, m) = n!/(n-m)!

组合数:C(n, m) = n!/(m!(n-m)!)

递推公式:C(n, m) = C(n-1, m-1) + C(n-1, m)(杨辉三角)

// 预处理组合数(小范围)
long long C[MAXN][MAXN];
void initCombination(int n) {
for (int i = 0; i <= n; i++) {
C[i][0] = 1;
for (int j = 1; j <= i; j++) {
C[i][j] = (C[i-1][j-1] + C[i-1][j]) % MOD;
}
}
}
// Lucas定理(p为质数,n,m很大)
long long lucas(long long n, long long m, long long p) {
if (m == 0) return 1;
return C[n % p][m % p] * lucas(n / p, m / p, p) % p;
}

6.2 卡特兰数#

定义:第 n 个卡特兰数 Cat(n) = C(2n, n) / (n + 1)

应用场景

  • n 对括号的合法匹配方案数
  • n 个节点构成的二叉树个数
  • n × n 方格从 (0,0) 走到 (n,n) 且不穿过对角线的路径数

递推公式:Cat(n) = Σ Cat(i) × Cat(n-1-i),其中 i = 0 to n-1

long long catalan[MAXN];
void initCatalan(int n) {
catalan[0] = catalan[1] = 1;
for (int i = 2; i <= n; i++) {
catalan[i] = catalan[i-1] * (4 * i - 2) / (i + 1);
}
}

6.3 斯特林数#

第一类斯特林数 S(n, k):n 个元素分成 k 个环的方案数

递推:S(n, k) = S(n-1, k-1) + (n-1) × S(n-1, k)

第二类斯特林数 S(n, k):n 个元素分成 k 个非空集合的方案数

递推:S(n, k) = S(n-1, k-1) + k × S(n-1, k)

七、莫比乌斯反演#

7.1 莫比乌斯函数#

定义

  • μ(1) = 1
  • μ(n) = (-1)^k,若 n 为 k 个不同质数的乘积
  • μ(n) = 0,若 n 含有平方因子

性质:Σ μ(d) = [n == 1](d|n)

7.2 莫比乌斯反演#

定理:若 F(n) = Σ f(d)(d|n),则 f(n) = Σ μ(d) × F(n/d)(d|n)

int mu[MAXN];
void initMobius(int n) {
mu[1] = 1;
for (int i = 2; i <= n; i++) {
if (isPrime[i]) {
primes[cnt++] = i;
mu[i] = -1;
}
for (int j = 0; j < cnt && (long long)i * primes[j] <= n; j++) {
if (i % primes[j] == 0) {
mu[i * primes[j]] = 0;
break;
} else {
mu[i * primes[j]] = -mu[i];
}
}
}
}

经典应用:求 Σ Σ [gcd(i, j) == 1](1 ≤ i ≤ n, 1 ≤ j ≤ m)

八、原根与离散对数#

8.1 阶的概念#

定义:满足 a^r ≡ 1 (mod p) 的最小正整数 r 称为 a 模 p 的阶,记作 ord_p(a)。

8.2 原根#

定义:若 ord_p(g) = φ(p),则称 g 为模 p 的原根。

性质:若 g 是模 p 的原根,则 g^0, g^1, …, g^(φ(p)-1) 模 p 的余数恰好是所有与 p 互质的数。

应用:离散对数、指标运算

8.3 BSGS算法#

问题:求解 a^x ≡ b (mod p)(Baby Step Giant Step)

算法思想:设 x = im - j,其中 m = ⌈√p⌉,枚举 j,用哈希表存储 a^j,然后枚举 i 查找。

时间复杂度:O(√p)

map<long long, int> mp;
long long BSGS(long long a, long long b, long long p) {
mp.clear();
a %= p; b %= p;
if (b == 1) return 0;
int m = sqrt(p) + 1;
long long val = b;
for (int j = 0; j < m; j++) {
mp[val] = j;
val = val * a % p;
}
long long base = quickPow(a, m, p);
val = 1;
for (int i = 1; i <= m; i++) {
val = val * base % p;
if (mp.count(val)) {
return i * m - mp[val];
}
}
return -1; // 无解
}

九、Miller-Rabin素性测试#

9.1 算法原理#

基于费马小定理的改进:对于奇质数 p,p - 1 = 2^s × d(d为奇数),若 a^d ≢ 1 (mod p),则必存在 r ∈ [0, s-1] 使得 a^(2^r × d) ≡ -1 (mod p)。

时间复杂度:O(k log^3 n),k 为测试轮数

bool millerRabin(long long n, int times = 10) {
if (n < 2) return false;
if (n == 2 || n == 3) return true;
if (n % 2 == 0) return false;
// n - 1 = 2^s × d
long long d = n - 1, s = 0;
while (d % 2 == 0) {
d /= 2;
s++;
}
auto check = [&](long long a) {
long long x = quickPow(a, d, n);
if (x == 1 || x == n - 1) return true;
for (int i = 0; i < s - 1; i++) {
x = (__int128)x * x % n;
if (x == n - 1) return true;
}
return false;
};
// 测试多个底数
for (int i = 0; i < times; i++) {
long long a = rand() % (n - 3) + 2;
if (!check(a)) return false;
}
return true;
}

十、Pollard-Rho大数分解#

10.1 算法思想#

利用生日悖论的概率方法,寻找 n 的非平凡因子。通过伪随机函数 f(x) = (x^2 + c) mod n 生成序列,利用 Floyd 判圈算法找到重复。

时间复杂度:期望 O(n^(1/4))

long long pollardRho(long long n) {
if (n % 2 == 0) return 2;
long long c = rand() % (n - 1) + 1;
long long x = rand() % n, y = x;
long long d = 1;
auto f = [&](long long val) {
return ((__int128)val * val + c) % n;
};
while (d == 1) {
x = f(x);
y = f(f(y));
d = gcd(abs(x - y), n);
}
return d == n ? pollardRho(n) : d;
}
// 完全分解
void factorize(long long n, vector<long long> &factors) {
if (n == 1) return;
if (millerRabin(n)) {
factors.push_back(n);
return;
}
long long p = pollardRho(n);
factorize(p, factors);
factorize(n / p, factors);
}

十一、IOI级别应用题#

11.1 例题1:约数个数问题#

问题:给定 n 个正整数 a1, a2, …, an,求它们的乘积的约数个数模 10^9+7。

思路:分解质因数,利用约数个数公式 (k1+1)(k2+1)…(km+1),其中 ki 是第 i 个质因子的指数。

11.2 例题2:同余方程求解#

问题:求解 ax ≡ b (mod p),其中 p 不一定是质数。

思路

  1. 求 d = gcd(a, p)
  2. 若 d ∤ b,无解
  3. 否则将方程化简为 (a/d)x ≡ (b/d) (mod p/d)
  4. 用扩展欧几里得求解

11.3 例题3:线性递推优化#

问题:求 f(n) = c1×f(n-1) + c2×f(n-2) + … + ck×f(n-k) 的第 n 项。

思路:构造转移矩阵,用矩阵快速幂求解,时间复杂度 O(k^3 log n)。

11.4 例题4:多项式模运算#

问题:给定多项式 P(x) 和 Q(x),求 P(x) mod Q(x)。

思路:利用快速幂思想,结合多项式乘法(FFT/NTT优化),可在 O(n log^2 n) 内完成。

总结#

数论算法是算法竞赛的核心内容之一,从基础的质数筛、GCD到高级的Miller-Rabin、Pollard-Rho,每个知识点都有其独特的应用场景。掌握这些算法需要:

  1. 理解数学原理:证明和推导能加深理解
  2. 熟练代码实现:模板要能快速默写
  3. 注意边界条件:模运算、溢出、特殊情况
  4. 灵活应用组合:实际问题往往需要多个知识点结合

建议学习路径:

  • CSP-S/提高组:质数筛、GCD、快速幂、逆元、欧拉函数
  • 省选/NOI:扩展欧几里得、CRT、组合数学、莫比乌斯反演
  • IOI/国际赛:原根、BSGS、Miller-Rabin、Pollard-Rho

持续练习,不断总结,相信你能在数论这个美妙的领域取得优异成绩!

参考资料#

  • 《算法竞赛进阶指南》
  • 《具体数学》(Concrete Mathematics)
  • 《初等数论》(Elementary Number Theory)
  • OI Wiki: https://oi-wiki.org/math/number-theory/
  • Codeforces 数论专题题单
数论算法详解:从CSP-S到IOI
https://myblog-590.pages.dev/posts/algorithm-number-theory/
作者
谢星宇
发布于
2026-01-21
许可协议
CC BY-NC-SA 4.0