逆元的求法

逆元有三种计算方法,分别是扩展欧几里得、费马小定理推论(快速幂求法)以及线性递推法。

一、扩展欧几里得法:

1.推导:

众所周知,扩展欧几里得是求解二元一次方程的方法。因为逆元的定义为:如果a*b≡1(mod p),则:a、b在模p意义下互为逆元。

由此,可设k*p+1=a*b。

两边同减k*p,得:1=a*b-k*p。

因为正负没有关系,所以可以变为a*b+k*p=1。

因为我们知道a和p的值,所以可以把这个方程视为二元一次方程,可用扩展欧几里得求解。

2.代码:

#include 
using namespace std;
#define pr pair

int Gcd(int a, int b) //根据裴蜀定理,判断是否有逆元
{
	if(b == 0)
		return a;
	return Gcd(b, a % b);
} 

pr Exgcd(int a, int b) //扩展欧几里得,得出的答案的第一项是逆元
{
	if(b == 0)
		return pr (1, 0);
	pr t = Exgcd(b, a % b);
	return pr (t.second, t.first - a / b * t.second);
}

int Inv(int a, int p)
{
	pr t = Exgcd(a, p);
	return (t.first % p + p) % p; //避免负逆元和逆元太大
}

int main()
{
	int a, p;
	scanf("%d %d", &a, &p);
	if(Gcd(a, p) != 1)
		printf("No inv!");
	else
		printf("%d", Inv(a, p));
	return 0;
}

二、费马小定理(快速幂求法):

1.推导:

费马小定理:若a、p互质,则:a^p≡p(mod p)。

两边同除p,得:a^(p-1)≡1(mod p)。

左边拆分出一个a,得:a*a^(p-2)≡1(mod p)。

所以,a^(p-2)就是a的逆元,直接用快速幂求取即可。

2.代码:

#include 
using namespace std;
 
int n;
 
long long fastpow(int x, int a, long long p)
{
    long long t = x % p;
    long long ans = 1;
    while(a > 0)
    {
        if(a & 1)
            ans = ans * t % p;
        t = t * t % p;
        a >>= 1;
    }
    return ans;
}
 
int main()
{
    scanf("%d", &n);
    while(n--)
    {
        int a, p;
        scanf("%d %d", &a, &p);
        if(a % p)
            printf("%lld\n", fastpow(a, p - 2, p));
        else
            printf("impossible\n");
    }
    return 0;
}

三、线性递推法:

1.推导:

已知i、模数p,求inv(i)。

设k=p/i(下取整),r=p%i。

所以k*i+r=p。

所以k*i+r≡0(mod p)。

两边同除(i*r),得:k/r+1/i≡0(mod p)。

所以k*inv(r)+inv(i)≡0(mod p)。

两边同减(k*inv(r)),得:inv(i)≡-k*inv(r)(mod p)。

又因为k=p/i,r=p%i,所以inv(i)≡(-p/i)*inv(p%i)(mod p)。

所以inv(i)≡(p-p/i)*inv(p%i)(mod p)。

又因为inv(i)*i≡1(mod p),所以(p-p/i)*inv(p%i)*i≡1(mod p)。

所以inv(i)=(p-p/i)*inv(p%i)

注意最后的式子,我们可以看到:p%i比i小,因此可以把i从小到大枚举,就可以算出从0到maxi的每个数的逆元。因此,可以线性递推实现。

2.代码:

#include 
using namespace std;
 
int n, p;
int inv[10000002];
 
int main()
{
    scanf("%d %d", &n, &p);
    inv[0] = inv[1] = 1; //初始化
    for(int i = 2; i <= n; i++)
        inv[i] = (long long)(p - p / i) * inv[p % i] % p; //强转型,避免溢出;取模,避免逆元太大
    printf("%d\n", inv[n]);
    return 0;
}

你可能感兴趣的:(算法)