基础数论算法汇总
乘法逆元
逆元是模意义下的倒数,能够将模意义下无法直接计算的除法转化为乘法。先来总结一下常用的求单个逆元的方法:
扩展欧几里得
void exgcd(int a, int b, int& x, int& y) {
if (b == 0) return x = 1, y = 0, void();
exgcd(b, a % b, y, x);
y = y - a / b * x;
}
exgcd(a, p, inv, temp);
快速幂
根据费马小定理,当
下面两种方法用于求多个数的逆元:
线性求逆元
显然
代码实现比较简单:
inv[1] = 1
for i in range(2, n + 1):
k = p // i
j = p % i
inv[i] = (p - k) * inv[j] % p # p-k 避免出现负数
线性求任意数逆元
当要求逆元的数不连续时(如本题中的情况),可以通过前缀积处理。
//求前缀积 s[i]
s[0] = 1;
for (int i = 1; i <= n; i++) s[i] = (long long)s[i - 1] * i % p;
// 费马小定理求 n 个数积的逆元(也就是逆元的积)
sv[n] = qpow(s[n], p - 2, p);
// 逐个消去 a[i] 的逆元,得到逆元前缀积
for (int i = n; i > 1; i--) sv[i - 1] = (long long)sv[i] * a[i] % p;
// 对于每个逆元前缀积,消去 a[1...i-1] 的逆元积,得到 a[i] 的逆元
for(int i = 1; i <= n; i++) inv[i] = (long long)sv[i] * s[i - 1] % p;
这个题也可以直接通分:设
裴蜀定理
此题中,由裴蜀定理的推广得,
def gcd(a, b):
if b == 0:
return a
return gcd(b, a % b)
n = int(input())
a = list(map(abs, map(int, input().split())))
result = a[0]
for i in range(1, len(a)):
result = gcd(result, a[i])
print(result)
扩展欧几里得
【模板】同余方程
求关于
方程
假设我们已知一组整数
那么怎么求
类似欧几里得算法,递归边界是
此时的答案尚不符合题目“最小”的要求,需要进一步处理。设
void exgcd(int a, int b, int &x, int &y) {
if (!b) return x = 1, y = 0, void();
exgcd(b, a % b, y, x);
y = y - (a / b) * x;
}
exgcd(a, b, x, y);
x = (x % b + b) % b; // 调整答案范围
【模板】二元一次不定方程:没有整数解当且仅当 ,直接输出 -1
。
用 exgcd 解方程
接下来,由「同余方程」一题中的技巧,用 x=(x%b+b)%b, y=(y%a+a)%a;
求出
扩展欧拉定理
首先引入三种可以通过取模缩小幂指数的方法。
- 费马小定理:当
为质数且 时, ,所以有 ; - 欧拉定理:当
互质时, ,其中欧拉函数 为 中与 互质的数的个数。由此, ; - 扩展欧拉定理:当
时,
。
此题没有给出
来
int phi = m;
for (int p = 2; n > 0; p++) {
if (n % p != 0) continue;
phi -= phi / p; // 公式中 (1-1/p) 的变形
while (n % p == 0) n /= p;
}
下面延伸介绍一下欧拉函数的知识。除了公式,还有五种方法可以计算欧拉函数:
- 若
为质数, ; - 若
为质数, ,有 ,即 去掉了 共计 个数; - 积性:若
互质, ,因为此时 的质因子 均不相同,所以 ; - 不完全积性:若
为质数, 为 的倍数,则 与 的质因数种类上完全相同,有 ; - 若
为奇数,则 ,由方法 1,3 可推得。
结合方法 1,3,4,我们可以用线性筛的方法
for (int i = 2; i <= n; i++) {
if (!isNotPrime[i]) {
primes.push_back(i);
phi[i] = i - 1; // 方法 1
}
for (int j = 0; j < primes.size() && i * primes[j] > n; j++) {
isNotPrime[i * primes[j]] = true;
if (i % primes[j] == 0) {
phi[i * primes[j]] = phi[i] * primes[j]; // 方法 4
break; // 线性筛原理:只让合数被「最小」质因数筛掉
}
phi[i * primes[j]] = phi[i] * phi[primes[j]]; // 方法 3
}
}
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· PowerShell开发游戏 · 打蜜蜂
· 在鹅厂做java开发是什么体验
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战
2023-11-08 C语言变量分类