数论基础

【第三讲】

第三讲我们要学习的概念有:

  • 线性同余方程
  • 逆元
  • 费马小定理

一、线性同余方程

如下形式的方程我们称作线性同余方程

依据定义可知,该方程表示 等于 的值

将 模运算写成带余除法的形式

合并化简成线性不定方程

可以发现,线性同余方程和线性不定方程是可以相互转化的

回顾之前学过的 裴蜀定理 有解的 充要条件

也就是说 也是线性同余方程 有解的 充要条件

那么代码实现和解线性不定方程一样

为例,等价于求解

C++:

#include<bits/stdc++.h>
using namespace std;
int exgcd(int a, int b, int& x, int& y){
    x = 1; y = 0;
    int x2 = 0; int y2 = 1;
    while (b != 0){
        int q = a / b;
        tie(a, b) = make_tuple(b, a % b);
        tie(x, x2) = make_tuple(x2, x-x2 * q);
        tie(y, y2) = make_tuple(y2, y-y2 * q);
    }
    return a;
}

void solve(int a, int b, int c, int& x,int& y){
	int d = exgcd(a,b,x,y);
	if (c % d != 0) return;  // 无解
	x *= c / d; y *= c / d;
}

int main(){
	int x,y;
	solve(84,30,12,x,y);
	cout << x << " " << y << endl;
	return 0;
}

Python:

def exgcd(a,b):
    x1, y1 = 1, 0
    x2, y2 = 0, 1
    while b!=0:
        q = a // b
        a, b = b, a % b
        x1, x2 = x2, x1 - x2 * q
        y1, y2 = y2, y1 - y2 * q
    return a, x1, y1

def solve(a,b,c):
    d, x, y = exgcd(a,b)
    if c % d != 0:
        return -1  # 无解
    return x * c // d, y * c // d

二、逆元

2.1 逆元的定义

若整数 满足:存在整数 使得

则称 逆元 ,记为

逆元存在的条件是该线性同余方程有解,根据裴蜀定理,该方程有解的充要条件是 互质,即

2.2 逆元的性质

(1) 唯一性

互质,则 的逆元在模 意义下唯一,即所有逆元均相差 的倍数

(2) 逆元的逆元

的逆元的逆元是 本身,即

(3) 乘积的逆元

逆元均存在

互质(即 存在),则

(4) 幂的逆元

对于正整数 ,有

(5)负数的逆元

互质(即 的逆元 存在),则 的逆元满足

(6)等价性

,且 均与 互质(即逆元存在),则

互质,由模运算的定义 ,则

(7) 消去律

互质(即 存在),且 ,则

(8) 封闭性

互质,则 仍与 互质

2.3 逆元的计算方法

2.3.1 扩展欧几里得算法

扩展欧几里得算法可直接求解不定方程 ,其中 的解即为 的逆元

示例: 求3模7的逆元

因为 ,所以逆元存在

解方程 ,通过扩展欧几里得算法得到

def exgcd(a, b):
    # 扩展欧几里得算法,返回 (gcd(a,b), x, y) 满足 ax + by = gcd(a,b)
    x1, y1 = 1, 0
    x2, y2 = 0, 1
    while b != 0:
        q = a // b
        a, b = b, a % b
        x1, x2 = x2, x1 - x2 * q
        y1, y2 = y2, y1 - y2 * q
    return a, x1, y1

def mod_inverse(a, m):
    d, x, y = exgcd(a, m)
    if d != 1:
        return None  # 逆元不存在(a与m不互质)
    return x % m  # 调整为模m的最小正逆元

# 示例:求3模7的逆元
print(mod_inverse(3, 7))  # 输出:5

2.3.2 费马小定理

是质数,且 不是 的倍数(即 ),则

两边同时除以 可得

即当 为质数时, 的逆元为

示例:求 5 模 7 的逆元

依据费马小定理,逆元为 =

, ,故逆元为

验证:

def inverse(a, p):
    # 费马小定理求逆元,p必须是质数且a不是p的倍数
    if a % p == 0:
        return None  # 逆元不存在
    return pow(a, p-2, p)

# 示例:求5模7的逆元(7是质数)
print(inverse(5, 7))  # 输出:3

一般来说,使用费马小定理进行逆元计算会出现非常的大的幂计算,这时候我们需要采用 快速幂算法 来加速计算,

由于python中的 pow 函数底层已经采用快速幂算法进行了优化,故此省略,详细的快速幂算法参考 数论基础十讲第五讲

2.3.3 欧拉定理

事实上,费马小定理是欧拉定理的一种特殊情况,由于篇幅限制,在此仅介绍欧拉定理如何计算逆元,不做深入探究,详细内容见 数论基础入门第四讲

为欧拉函数,表示小于等于 且与 互质的整数的个数

互质,则:

两边同时除以 可得:

的逆元为

联系

为质数 时, ,欧拉定理退化为费马小定理,因此费马小定理是欧拉定理的特例

2.3.4 线性递推求逆元

在需要计算 的逆元时(例如组合数计算、多项式问题),逐个用扩展欧几里得或者费马小定理计算效率较低( ),而 线性递推求逆元 可将效率优化至 ,适用模数 为质数的场景

线性递推公式

是质数, 是区间 中的整数, 表示 的逆元,则有递推关系:

推导:

对于 ,设 ,其中 ,因 是质数且 ,故

意义下, ,整理得

两边同时乘上 (因 已通过递推求得 )

带入 ,并调整符号,避免负数

C++:

#include <bits/stdc++.h>
using namespace std;

// 线性递推求逆元:求1~n模p的逆元(p必须是质数,且n < p)
vector<int> linear_inv(int n, int p) {
    vector<int> inv(n + 1);  // 存储inv[1]到inv[n]的逆元
    inv[1] = 1;  // 1的逆元是1(1*1≡1 mod p)
    for (int i = 2; i <= n; ++i) {
        // 核心公式:inv[i] = (p - p/i) * inv[p%i] % p
        // 用1LL将中间结果转换为long long,避免乘法溢出
        inv[i] = ( (p - p / i) * 1LL * inv[p % i] ) % p;
    }
    return inv;
}

int main() {
    int p = 7;  // 质数模
    int n = 5;  // 求1~5的逆元
    vector<int> inv = linear_inv(n, p);
    
    // 输出1~5模7的逆元
    for (int i = 1; i <= n; ++i) {
        cout << inv[i] << " ";
    }
    // 输出:1 4 5 2 3
    return 0;
}

Python:

def linear_inv(n, p):
    # 求1~n模p的逆元(p为质数,且n < p)
    inv = [0] * (n + 1)
    inv[1] = 1
    for i in range(2, n + 1):
        inv[i] = (p - p // i) * inv[p % i] % p
    return inv

# 示例:求1~5模7的逆元
p = 7
inv = linear_inv(5, p)
print(inv[1:6])  # 输出:[1,4,5,2,3](验证:2*4=8≡1, 3*5=15≡1, 4*2=8≡1, 5*3=15≡1 mod7)

2.3.5 线性递推求阶乘的逆元

如果我们需要预处理 阶乘的逆元 ,即 ,有如下公式

推导

根据逆元定义,有

同时,由于

两边取逆元

两边同时乘以 ,得到

2.4 逆元的应用

逆元的 核心作用将模运算中的“除法”转化为”乘法”

例如,计算 ,若 ,存在逆元,则

又例如,在 中,由于阶乘的除法无法直接在模运算中进行,需要用逆元转换成乘法

C++:

#include <bits/stdc++.h>
using namespace std;

typedef long long LL;
const int MAXN = 1e5 + 5;  // 最大范围,可根据需求调整
const int MOD = 1e9 + 7;   // 模值,通常为质数

LL fact[MAXN];     // 预处理阶乘:fact[i] = i! mod MOD
LL inv_fact[MAXN]; // 预处理阶乘逆元:inv_fact[i] = (i!)^(-1) mod MOD

// 预处理阶乘和阶乘逆元
void preprocess() {
    fact[0] = 1;
    for (int i = 1; i < MAXN; i++) {
        fact[i] = fact[i-1] * i % MOD;
    }
    
    // 计算最大阶乘的逆元(费马小定理)
    inv_fact[MAXN-1] = 1;
    for (int i = MAXN-2; i >= 0; i--) {
        inv_fact[i] = inv_fact[i+1] * (i+1) % MOD;
    }
}

// 计算组合数 C(n, k) mod MOD
LL comb(int n, int k) {
    if (k < 0 || k > n) return 0;  // 边界情况
    return fact[n] * inv_fact[k] % MOD * inv_fact[n-k] % MOD;
}

int main() {
    preprocess();  // 预处理阶乘和逆元
    
    // 示例:计算 C(5, 2) mod (1e9+7)
    int n = 5, k = 2;
    cout << "C(" << n << ", " << k << ") mod " << MOD << " = " << comb(n, k) << endl;
    // 输出:10
    
    return 0;
}

时间复杂度

  • 预处理:
  • 查询:

总复杂度为

Python:

MOD = 10**9 + 7  # 模值(质数)
MAXN = 10**5 + 5  # 最大阶乘范围,可根据需求调整

# 预处理阶乘:fact[i] = i! mod MOD
fact = [1] * MAXN
for i in range(1, MAXN):
    fact[i] = fact[i-1] * i % MOD

# 预处理阶乘逆元:inv_fact[i] = (i!)^(-1) mod MOD
inv_fact = [1] * MAXN
# 先计算最大阶乘的逆元(费马小定理:a^(MOD-2) mod MOD 是a的逆元)
inv_fact[MAXN-1] = pow(fact[MAXN-1], MOD-2, MOD)
# 倒推计算 smaller 的阶乘逆元
for i in range(MAXN-2, -1, -1):
    inv_fact[i] = inv_fact[i+1] * (i+1) % MOD

# 计算组合数 C(n, k) mod MOD 的函数
def comb(n, k):
    if k < 0 or k > n:  # 边界情况:k无效时返回0
        return 0
    return fact[n] * inv_fact[k] % MOD * inv_fact[n - k] % MOD

# 示例:计算 C(5, 2) mod 1e9+7
print(comb(5, 2))  # 输出:10(因5!/(2!·3!)=120/(2·6)=10)