数论基础
【第三讲】
第三讲我们要学习的概念有:
- 线性同余方程
- 逆元
- 费马小定理
一、线性同余方程
如下形式的方程我们称作线性同余方程
依据定义可知,该方程表示 模
等于
模
的值
将 模运算写成带余除法的形式
合并化简成线性不定方程
令
可以发现,线性同余方程和线性不定方程是可以相互转化的
回顾之前学过的 裴蜀定理 , 是
有解的 充要条件
也就是说 也是线性同余方程
有解的 充要条件
那么代码实现和解线性不定方程一样
以 为例,等价于求解
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)