【进阶数论】模乘逆元
【进阶数论】模乘逆元
逆元的定义
若 \(ax \equiv 1 \pmod{p}\) 且 \(a \perp b\),则称 \(x\) 为 \(a\) 的逆元。记作 \(a^{-1} \pmod{p}\)。
同时,我们也可以称 \(x\) 为 \(a\) 在 \(\bmod\ p\) 意义下的倒数。
逆元的求法
裴蜀定理(Bézout's Lemma)
在推导这个定理之前,我们要先了解一下裴蜀定理(Bézout's Lemma)和朴素欧几里得算法。
裴蜀定理: 对于任意的 \(a, b\ (a,b\in \Z)\),有 \(x, y\ (x,y\in \Z)\) 使得 \(ax + by = \gcd(a, b)\)。
证明:
- 考虑 \(a \perp b\)。这时 \(\gcd(a,b)=1\)。提取 \(\gcd(a,b)\),令 \(g=\gcd(a,b)\) 得:
两个数乘积为 \(1\),要么同为 \(1\),要么同为 \(-1\),因为任何两个数的最大公约数不可能为负数,所以 \(ax + by = \gcd(a, b)=1\)。
- 考虑 \(a\) 和 \(b\) 不互素的情况。此时设 \(g>1\),令 \(a'=\frac{a}{g},b'=\frac{b}{g}\)。所以此时 \(\gcd(a',b')=1\) 且 \(a' \perp b'\)。所以此时有:
等式两边同乘以 \(g\),得:
即
令 \(x=x',y=y'\),得:
多个整数的推广:
对于 \(a_1,a_2,a_3,a_4, \cdots , a_n \ (a \in \Z)\) 和 \(x_1,x_2,x_3,x_4, \cdots , x_n \ (x \in \Z)\),令 \(g=\gcd(a_1,a_2,a_3, \cdots a_n)\),有:
【例1】P4549 【模板】裴蜀定理
给定一个包含 \(n\) 个元素的整数序列 \(A\),记作 \(A_1,A_2,A_3,...,A_n\)。
求另一个包含 \(n\) 个元素的待定整数序列 \(X\),记 \(S=\sum\limits_{i=1}^nA_i\times X_i\),使得 \(S>0\) 且 \(S\) 尽可能的小。
这就是上述推论的模板。所以答案即为:
那么有一个问题:当 $A_i < 0 $ 如何处理。实际上我们知道,根据上述推论,\(\sum\limits_{i=1}^nA_i\times X_i\) 的值与 \(X_i\) 的值无关。所以我们直接将 \(A_i\) 里的负号移到 \(X_i\) 中。是不影响结果的。也就是说 \(-A_i\) 等价于 \(A_i\)。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
ll n;
int main() {
cin>>n;
ll ans=0,a;
for(int i=1;i<=n;i++) {
cin>>a;
ans=__gcd(ans,abs(a));
}
cout<<ans;
}
朴素欧几里得算法(Euclidean algorithm)
\(\gcd(a,b)=\gcd(b , a \bmod b)\)。
int gcd(int a,int b){
if(b==0) return a;
return gcd(b,a%b);
}
当然,C++ 的库函数中有 __gcd()
函数,其时间复杂度与上述代码相同。可以放心使用。
扩展欧几里得算法(Extended Euclidean algorithm)
考虑求 \(ax+by=1\) 中 \(x\) 的最小正整数解。由裴蜀定理证明的第一个过程可知,必须满足 \(a \perp b\)。如果不满足则方程无解。则有如下推导过程:
因为 \(a \perp b\),所以:
由朴素欧几里得定理,得:
由裴蜀定理中 \(ax+by=\gcd(a,b)\),得 \(\gcd(a,b)=ax+by\),即:
根据模运算的定义得:
合并同类项,得:
所以:
【例 2】P1082 [NOIP 2012 提高组] 同余方程
求关于 $ x$ 的同余方程 $ a x \equiv 1 \pmod {b}$ 的最小正整数解。
显然这是一个求逆元的题。将 $ a x \equiv 1 \pmod {b}$ 换一种表达方式,得:
可以将其化为:
因此就可以变成扩展欧几里得算法的标准形式:
接下来运用扩欧算法求即可。
但是我们要注意,这个求得的 \(x\) 可能为负数,也有可能不是最小正整数解。
考虑将 \(x\) 转化为符合条件的最小正整数。
有结论:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
ll expgcd(ll a,ll b,ll &x,ll &y){
if (b==0){x=1,y=0; return a;}
ll p=expgcd(b,a%b,y,x);
y-=(a/b)*x;
return p;
}
ll inv(ll a,ll n){
ll x,y;
expgcd(a,n,x,y);
x=(x%n+n)%n;
return x;
}
int main(){
ll a,b;
cin>>a>>b;
cout<<inv(a,b);
return 0;
}