乘法逆元
原理
什么是乘法逆元???
- 若 a x ≡ 1 ( m o d p ) ax\equiv 1\pmod p ax≡1(modp),则 x 就称为 a 在 mod p意义下的乘法逆元
为什么要求乘法逆元?
- 常用于分数取模。同余定理对加减乘都满足,而对于除不满足,当数据都是mod下进行计算的时候突然出现了除的操作,必须求其乘法逆元来将除操作转化为乘逆元的操作
- 转化:
- a n s ≡ a b ( m o d p ) , 1 ≡ b x ( m o d p ) < = > a n s ≡ a x ( m o d p ) ans\equiv\frac{a}{b}\pmod p,1\equiv bx\pmod p<=>ans\equiv ax\pmod p ans≡ba(modp),1≡bx(modp)<=>ans≡ax(modp)
a模p的意义下有乘法逆元当且仅当 gcd(a,p)=1
如何求乘法逆元???
一.拓展欧几里得
算法学习:
- 拓展欧几里得求解的问题: a x + b y = g c d ( a , b ) ax+by=gcd(a,b) ax+by=gcd(a,b)。
- 如何求解 x , y x,y x,y:证明过程略,代码如下
- x,y的通解:
- d x = b / g c d ( a , b ) , d y = a / g c d ( a , b ) d_x=b/gcd(a,b),d_y=a/gcd(a,b) dx=b/gcd(a,b),dy=a/gcd(a,b)
- x = x 1 + t d x , y = y 1 − t d y x=x_1+td_x,y=y1-td_y x=x1+tdx,y=y1−tdy
- 特殊解:
- x x x 的最小非负整数解: x 1 + t d x > = 0 x_1+td_x>=0 x1+tdx>=0, = > t > = − x 1 / d x =>t>=-x_1/d_x =>t>=−x1/dx(向上取整)
- 【也可以用模运算快速求得】
- y y y 的最小非负整数解: y 1 − t d y > = 0 y_1-td_y>=0 y1−tdy>=0, = > t < = y 1 / d y =>t<=y_1/d_y =>t<=y1/dy(向下取取整)
- 【也可以用模运算快速求得】
- 正整数解中 x x x 或 y y y 的最大值:很显然, x x x 越大 y y y 越小, x x x 越小 y y y 越大。当 x x x 取到最小非负整数解对应的 t t t 带入 y y y 的解形式,即可得到 y y y 的最大值。 x x x 同理
- 正整数解的个数: ( x m a x − x m i n ) / d x (x_{max}-x_{min})/d_x (xmax−xmin)/dx
long long x,y;
void EX_gcd(long long a,long long b){
if(b==0){
x=1;
y=0;
return;
}
EX_gcd(b,a%b);
long long t=x;
x=y;
y=t-a/b*y;
}
int main(){
EX_gcd(a,b);//此时的(x,y)为一组特解
x=(x%b+b)%b;//简单处理使 x变成最小正整数
}
求逆元
- 当 a 与 b 互质,求解 a x + b y = g c d ( a , b ) ax+by=gcd(a,b) ax+by=gcd(a,b)
- 等价于求解 a x + b y = 1 ax+by=1 ax+by=1
- 可以看作求解 a x ≡ 1 ( m o d b ) ax\equiv1\pmod b ax≡1(modb)
- 优点: 可以在 a 与 b 互质,但 b 不是质数时成立。(这也是中国剩余定理不能用费马小定理只能用拓欧求的原因)
二.费马小定理(适用于 p 为质数,速度快)
- 欧拉定理 a φ ( p ) ≡ 1 ( m o d p ) a^{φ(p)}\equiv 1\pmod p aφ(p)≡1(modp)
- 当 p 为质数时, φ ( p ) = p − 1 φ(p)=p-1 φ(p)=p−1
- 则就有了费马小定理: a p − 1 ≡ 1 ( m o d p ) a^{p-1}\equiv 1\pmod p ap−1≡1(modp)
- = > a × a p − 2 = 1 ( m o d p ) =>a×a^{p-2}=1\pmod p =>a×ap−2=1(modp)
- 则 a p − 2 a^{p-2} ap−2 就是 a 模 p 意义下的乘法逆元 ,ksm求 a p − 2 a^{p-2} ap−2 即可
long long ksm(long long a,long long b){
long long ans=1;
while(b){
if(b%2)ans=ans*a%p;
a=a*a%p;
b/=2;
}
return ans;
}
int main(){
cin>>a>>b>>p;
cout<<ksm(a,p-2);
}
三.线性递推求逆元
- 用途: 可以 O ( n ) O(n) O(n) 预处理出 [ 1 , n ] [1,n] [1,n] 所有数的关于模 p 的乘法逆元。
- 公式: i n v [ i ] = ( p − p / i ) × i n v [ p % i ] % p , i n v [ 1 ] = 1 inv[i]=(p-p/i)×inv[p\%i]\%p,inv[1]=1 inv[i]=(p−p/i)×inv[p%i]%p,inv[1]=1
long long inv[N];
int main(){
int n,p;
cin>>n>>p;
inv[1]=1;
for(int i=2;i<=n;i++)inv[i]=(p-p/i)*inv[p%i]%p;
for(int i=1;i<=n;i++)printf("%d\n",inv[i]);
return 0;
}
四.多次查询,快速求组合数
- 组合数的求法: C n m = n ! m ! × ( n − m ) ! C_n^m=\frac{n!}{m!\times(n-m)!} Cnm=m!×(n−m)!n!
- 预处理 f [ i ] = i ! f[i]=i! f[i]=i!, C n m = f [ n ] × i n v [ f [ m ] ] × i n v [ f [ n − m ] ] C_{n}^m=f[n]\times {inv[f[m]]\times inv[f[n-m]]} Cnm=f[n]×inv[f[m]]×inv[f[n−m]]
- 如何预处理 i n v [ f [ n ] ] inv[f[n]] inv[f[n]] 呢?
- i n v [ f [ n ] ] = i n v [ ∏ i = 1 n i ] = ∏ i = 1 n i n v [ i ] inv[f[n]]=inv[\prod_{i=1}^n i]=\prod_{i=1}^n inv[i] inv[f[n]]=inv[∏i=1ni]=∏i=1ninv[i]
long long C(long long n,long long m){
return f[n]*inv[m]%mod*inv[n-m]%mod;
}
int main(){
f[0]=1;
for(int i=1;i<=N;i++)f[i]=f[i-1]*i%mod;
inv[1]=1;
for(int i=2;i<=N;i++)inv[i]=(mod-mod/i)*inv[mod%i]%mod;
inv[0]=1;
for(int i=1;i<=N;i++)inv[i]=inv[i-1]*inv[i]%mod;
}
五.线性递推求逆元(升级版)
用途:
- 普通的线性递推求逆元只能求连续的1~n的逆元。该方法可以求任意n个数关于模 p 的乘法逆元。
原理介绍如下:
- 已知 S n = ∏ i = 1 n a i , S n = S n − 1 × a n S_n=\prod_{i=1}^n a_i,S_n=S_{n-1}\times a_n Sn=∏i=1nai,Sn=Sn−1×an
- 则有 ( S n − 1 ) − 1 = ( S n ) − 1 × a n (S_{n-1})^{-1}=(S_n)^{-1}\times a_n (Sn−1)−1=(Sn)−1×an
- 则有 ( a n ) − 1 = S n − 1 × ( S n ) − 1 (a_n)^{-1}=S_{n-1}\times (S_n)^{-1} (an)−1=Sn−1×(Sn)−1
步骤如下:
- 预处理 S 1 , S 2 , … … , S n S_1,S_2,……,S_n S1,S2,……,Sn,并求 S n S_n Sn 的逆元
- 递推求出 S n − 1 , S n − 2 … … S 2 , S 1 S_{n-1},S_{n-2}……S_2,S_1 Sn−1,Sn−2……S2,S1 的逆元
- 递推求出 a 1 , a 2 … … a n − 1 , a n a_1,a_2……a_{n-1},a_{n} a1,a2……an−1,an 的逆元
const int N=1e6;
long long p,n;
long long s[N],a[N],t[N];
//t[N]先是s[N]的逆元,后是a[N]的逆元
long long ksm(long long a,long long b){
long long x=1;
while(b){
if(b%2)x=x*a%p;
a=a*a%p;
b/=2;
}
return x;
}
int main(){
cin>>n>>p;
for(int i=1;i<=n;i++)cin>>a[i];
s[0]=1;
for(int i=1;i<=n;i++)s[i]=s[i-1]*a[i]%p;
t[n]=ksm(s[n],p-2);//求s[n]的逆元
for(int i=n;i>=2;i--)t[i-1]=t[i]*a[i]%p;//递推求s[i]的逆元
for(int i=1;i<=n;i++)t[i]=t[i]*s[i-1]%p;//递推求a[i]的逆元
for(int i=1;i<=n;i++)cout<<t[i]<<endl;
}
二次剩余
x 2 ≡ a ( % p ) x^2\equiv a(\%p) x2≡a(%p)