数论-逆元

逆元

1.什么是逆元?

对于正整数 am,如果有ax ≡ 1 (mod m),且am互质,那么方程中的 x 最小正整数解叫做模的逆元。

(P.S. 若a与m不互质,将不存在逆元)

2.为什么需要逆元

当求解公式:(a/b)%m 时,因b可能会过小,会出现爆精度的情况,所以需变除法为乘法,然后乘法取模进行缩小数据范围。

设c是b的逆元,则有b*c ≡ 1(mod m);

(a/b)%m = (a/b)*1%m = (a/b)*b*c%m = a*c(mod m);

就有了a/b≡ a*c (mod m)

即 a/b 的模等于 a*c 的逆元的模,所以就是利用下面的算法为找出这个逆元,就可以解决除法逆元的问题;

3.现在来看一个逆元最常见问题,求如下表达式的值

在这里插入图片描述

逆元一般用扩展欧几里得算法来求得,如果为素数,那么还可以根据费马小定理得到逆元为 am-2(mod m)

1.拓展欧几里得法(裴蜀定理)

根据拓展欧几里得可以求出:ax + py = 1 (mod p) 的x,y的解,然后写出 ax ≡ 1 (mod p)
带入求得:

#include<stdio.h>
int exgcd(int a,int b,int &x,int &y){
	if(b == 0) {x = 1,y = 0;return a;}
	int d = exgcd(b,a%b,y,x);
	y -= x*(a/b);
	return d;
}
int main()
{
	int a,p,n;
	int x,y;
	scanf("%d",&n);
	for(int i = 1;i <= n;i++){
		scanf("%d %d",&a,&p);
		int d = exgcd(a,p,x,y);	//拓展欧拉定理
		while(x < 0) x += p;	//因为要取模,所以要变成正数 
		printf("%d\n",x);	//得出 ax = 1(mod p)的x结果 
	}
	return 0;
}

时间复杂度为:O(log2n)
局限性:a和p要互质


2.小费马定理

当 p 为素数时,根据费马小定理得:

a(p-1) = 1(mod p)

推出得:

a*a(p-2) = 1 (mod p)

带入快速幂,即可求得 a 的逆元。

#include<stdio.h>
//	a*a^(p-2) = 1(mod p)
typedef long long ll;
ll a,p;
ll ksm(ll a,ll base){ll res = 1;while(base){if(base&1) res = a*res%p;a = a*a%p;base >>= 1;}return res;}
int main(){
	int n;
	scanf("%d",&n);
	for(int i = 1;i <= n;i++){
		scanf("%lld %lld",&a,&p);
		ll x = ksm(a,p-2)%p;	//快速幂
		printf("%lld\n",x);
	}	
	return 0;
}

时间复杂度为:O(log2n)
局限性:a和p要互质,且p要为素数

3.欧拉定理求逆元:

欧拉定理:若an为正整数,且a , n 互质,则 aφ(n) ≡ 1 (mod m)
在这里插入图片描述
可以看出,费马小定理其实就是欧拉定理的一种情况:即n为素数时,φ(n) = n−1,便有了 an−1 ≡ 1 (mod n)

ll euler(ll n){
    ll ans = n;
    for(int i = 2; i*i <= n; i++){
        if(!(n%i)){
            ans = ans/i*(i - 1);
            while(!(n%i)) n /= i;
        }
    }
    if(n > 1) ans = ans/n*(n - 1);
    return ans;
}
ll inv_euler(ll a, ll n) {
	return ksm(a,euler(n)-1,n);
}

局限性:a和p要互质

4.递推法

对于大量的逆元需求时,用递推法可以线性筛出逆元,当我们需要求 1 - (p-1) 模 p 的逆元时,可以筛出逆元

前提为:p 为素数

推导过程如下:
设:
t = p / i
k = p mod i
得到有:t*i + k (mod p)

k = -t*i (mod p)
左右同除 i*k ,并且带入 t,k得到
inv[i] = - p/i*inv[p%i] (mod p)

注意,当 a < 0 时,a % p 需要变成 (a + p)%p,这样就可以把原式的 mod p 消掉,
得到 inv[i] = p - p/i*inv[p%i]

这样就可以进行线性的递推

#include<stdio.h>
typedef long long ll;
const int maxn = 10001;
int inv[maxn] = {0};

int main()
{
	ll n,p;
	scanf("%lld %lld",&n,&p); 
	inv[1] = 1;
	printf("第三个参数表示是否普存在逆元\n");
	printf("inv[1] = 1 1\n");
	for(int i = 2;i <= n;i++){
		inv[i] = (p-p/i)*inv[p%i]%p;
		printf("inv[%d] = %d %d\n",i,inv[i],i*inv[i]%p);
	}
	
	return 0;
}

时间复杂度为:n * O(log2n)


5.求阶乘的逆元在这里插入图片描述

这里还要知道

s/a-1 = s/a-1/a*a = s/(a-1*a)a = sa

inv[maxn] = ksm(fac[maxn],p-2);  
for(ll i=maxn-1;i>=0;i--)  
    inv[i]=(inv[i+1]*(i+1))%p;

参考博客
参考博客

  • 4
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值