乘法逆元的知识 (各种方法)

乘法逆元

原理

什么是乘法逆元???

  • a x ≡ 1 ( m o d p ) ax\equiv 1\pmod p ax1(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 ansba(modp),1bx(modp)<=>ansax(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=y1tdy
  • 特殊解:
  • 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 y1tdy>=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 (xmaxxmin)/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 ax1(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)=p1
  • 则就有了费马小定理: a p − 1 ≡ 1 ( m o d p ) a^{p-1}\equiv 1\pmod p ap11(modp)
  • = > a × a p − 2 = 1 ( m o d p ) =>a×a^{p-2}=1\pmod p =>a×ap2=1(modp)
  • a p − 2 a^{p-2} ap2 就是 a 模 p 意义下的乘法逆元 ,ksm求 a p − 2 a^{p-2} ap2 即可
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]=(pp/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!×(nm)!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[nm]]
  • 如何预处理 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=1naiSn=Sn1×an
  • 则有 ( S n − 1 ) − 1 = ( S n ) − 1 × a n (S_{n-1})^{-1}=(S_n)^{-1}\times a_n (Sn1)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=Sn1×(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 Sn1,Sn2S2,S1 的逆元
  • 递推求出 a 1 , a 2 … … a n − 1 , a n a_1,a_2……a_{n-1},a_{n} a1,a2an1,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) x2a(%p)

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值