文章目录

数论

数论

参考资料:李煜东《算法竞赛进阶指南》0x30 数学知识


目录


0x31 质数

1. 基本概念

质数(素数):若一个大于 \(1\) 的正整数,无法被除了 \(1\) 和它本身以外的任何正整数整除,则称该数为质数。否则称为合数。

性质

  • \(1\) 既不是质数也不是合数。
  • 质数有无穷多个。

素数定理(Prime Number Theorem)

素数分布理论的中心定理。设 \(x \ge 1\),以 \(\pi(x)\) 表示不超过 \(x\) 的素数的个数,当 \(x \to \infty\) 时:

\[\pi(x) \sim \frac{x}{\ln x} \]

也就是说,不超过 \(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\) 都可以唯一分解成有限个素数(质数)的幂的乘积:

\[N = p_1^{c_1} \times p_2^{c_2} \times p_3^{c_3} \times \cdots \times p_r^{c_r} \]

这里 \(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\)
  • 以此类推。

故最终结果为:

\[\begin{aligned} & \left(\left\lfloor \frac{n}{p} \right\rfloor - \left\lfloor \frac{n}{p^2} \right\rfloor\right) \times 1 + \left(\left\lfloor \frac{n}{p^2} \right\rfloor - \left\lfloor \frac{n}{p^3} \right\rfloor\right) \times 2 + \left(\left\lfloor \frac{n}{p^3} \right\rfloor - \left\lfloor \frac{n}{p^4} \right\rfloor\right) \times 3 + \cdots \\ = \ & \left\lfloor \frac{n}{p} \right\rfloor + \left\lfloor \frac{n}{p^2} \right\rfloor + \left\lfloor \frac{n}{p^3} \right\rfloor + \cdots \end{aligned} \]

代码实现

// 求 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. 先筛出 \([1, \sqrt{R}]\) 中的所有质数。
  2. 用这些质数去标记区间 \([L, R]\) 中的合数。
  3. 区间内未被标记的就是质数。
// 核心思想:用 [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}\),则:

约数个数

\[d(N) = (c_1 + 1) \cdot (c_2 + 1) \cdots (c_m + 1) = \prod_{i=1}^{m} (c_i + 1) \]

理解:每个质因子 \(p_i\) 可以选 \(0 \sim c_i\) 个,共 \(c_i + 1\) 种选择。

约数和

\[\sigma(N) = (1 + p_1 + p_1^2 + \dots + p_1^{c_1}) \cdot (1 + p_2 + p_2^2 + \dots + p_2^{c_2}) \cdots = \prod_{i=1}^{m} \sum_{j=0}^{c_i} p_i^j \]

理解:乘法分配律展开后就是所有约数的和。


4. 最大公约数(GCD)

定义

\(d\) 同时整除 \(a\)\(b\),则称 \(d\)\(a, b\) 的公约数。所有公约数中最大的称为最大公约数,记为 \(\gcd(a, b)\)

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

\[\gcd(a, b) = \gcd(b, a \bmod 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)

\[\operatorname{lcm}(a, b) = \frac{a \cdot b}{\gcd(a, b)} \]

注意:计算时先除后乘,避免溢出:a / gcd(a, b) * b


5. 更相减损术(二进制优化)

对于大整数(高精度),取模运算较慢,可以用更相减损术:

\[\gcd(a, b) = \gcd(b, a - b) \quad (a > b) \]

优化(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}\),则:

\[\varphi(N) = N \cdot \frac{p_1 - 1}{p_1} \cdot \frac{p_2 - 1}{p_2} \cdots \frac{p_m - 1}{p_m} = N \cdot \prod_{i=1}^{m} \left(1 - \frac{1}{p_i}\right) \]

理解(容斥原理):从 \(1 \sim N\) 中去掉所有 \(p_i\) 的倍数,加回同时是 \(p_i p_j\) 倍数的,……

性质

  1. \(n\) 是质数,则 \(\varphi(n) = n - 1\)
  2. \(p\) 是质数,\(n = p^k\),则 \(\varphi(n) = p^k - p^{k-1} = (p-1) \cdot p^{k-1}\)
  3. 欧拉函数是积性函数:若 \(\gcd(a, b) = 1\),则 \(\varphi(ab) = \varphi(a) \cdot \varphi(b)\)
  4. \(\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 b \pmod 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\),使得:

\[ax + by = \gcd(a, b) \]

扩展欧几里得算法

求一组整数 \(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\),代入得:

\[bx' + (a - \lfloor a/b \rfloor \cdot b)y' = d \]

\[ay' + b(x' - \lfloor a/b \rfloor \cdot y') = d \]

因此 \(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)\),则通解为:

\[x = x_0 + k \cdot \frac{b}{d}, \quad y = y_0 - k \cdot \frac{a}{d} \quad (k \in \mathbb{Z}) \]


3. 线性同余方程

定义

形如 \(ax \equiv b \pmod m\) 的方程称为线性同余方程。

求解方法

方程等价于 \(ax - b = km\),即 \(ax + m(-k) = b\),也就是求 \(ax + my = b\) 的整数解。

由 Bezout 定理,方程有解当且仅当 \(\gcd(a, m) \mid b\)

步骤

  1. 用扩展欧几里得求 \(ax_0 + my_0 = d\),其中 \(d = \gcd(a, m)\)
  2. \(d \nmid b\),无解。
  3. 否则,\(x = x_0 \cdot \frac{b}{d} \bmod m\) 是一个特解。
  4. 通解:\(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^{p-1} \equiv 1 \pmod 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\) 下的逆元:

\[i^{-1} \equiv -\left\lfloor \frac{p}{i} \right\rfloor \cdot (p \bmod i)^{-1} \pmod 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)

问题

求解同余方程组:

\[\begin{cases} x \equiv a_1 \pmod {m_1} \\ x \equiv a_2 \pmod {m_2} \\ \quad \vdots \\ x \equiv a_n \pmod {m_n} \end{cases} \]

其中 \(m_1, m_2, \dots, m_n\) 两两互质。

解法

\(M = \prod m_i\)\(M_i = M / m_i\)\(t_i = M_i^{-1} \pmod {m_i}\),则解为:

\[x \equiv \sum_{i=1}^{n} a_i \cdot M_i \cdot t_i \pmod M \]

理解:每一项 \(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\),则:

\[a^{\varphi(m)} \equiv 1 \pmod m \]

费马小定理(欧拉定理的特例)

\(p\) 是质数且 \(p \nmid a\),则:

\[a^{p-1} \equiv 1 \pmod p \]

欧拉定理的推论

\(\gcd(a, m) = 1\),则 \(a^b \equiv a^{b \bmod \varphi(m)} \pmod m\)

更一般地(扩展欧拉定理):

\[a^b \equiv \begin{cases} a^b & b < \varphi(m) \\ a^{b \bmod \varphi(m) + \varphi(m)} & b \ge \varphi(m) \end{cases} \pmod m \]

(不需要 \(a, m\) 互质)



0x34 组合计数与容斥

1. 组合数

定义

\(n\) 个不同元素中取出 \(m\) 个的方案数:

\[\binom{n}{m} = \frac{n!}{m!(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\) 是质数,则:

\[\binom{n}{m} \equiv \binom{n \bmod p}{m \bmod p} \cdot \binom{\lfloor n/p \rfloor}{\lfloor m/p \rfloor} \pmod 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\) 是有限集合,则:

\[\left|\bigcup_{i=1}^{n} S_i\right| = \sum_{i} |S_i| - \sum_{i<j} |S_i \cap S_j| + \sum_{i<j<k} |S_i \cap S_j \cap S_k| - \dots + (-1)^{n+1} \left|\bigcap_{i=1}^{n} S_i\right| \]

口诀:奇加偶减(元素个数为奇数的交集加,偶数的减)。

经典应用

\([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. 莫比乌斯函数

定义

\[\mu(n) = \begin{cases} 1 & n = 1 \\ 0 & n \text{ 有平方因子} \\ (-1)^k & n = p_1 p_2 \dots p_k \text{(}k\text{ 个不同质因子)} \end{cases} \]

性质

  1. 莫比乌斯函数是积性函数。
  2. \(\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 润色

posted @ 2026-09-02 14:23  lvwangshu  阅读(29)  评论(0)    收藏  举报