利用扩展的欧几里得算法求逆元

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了利用扩展的欧几里得算法求逆元相关的知识,希望对你有一定的参考价值。

参考技术A

首先说一下逆元的定义。
存在一个数a使得a x对y进行取余运算,得到的值是一,则成为a是x的逆元。在数学中记做
a * x = 1(mod p)
例如x = 4,y = 11,3
x = 1(mod y),3 4=12,12 mod 11 = 1,3就是x的逆元。
对于求逆元这一操作在计算机领域主要用于非对称加密,如我们常见的RSA加密算法等。
那应该求得这个逆元呢,我们知道,再求两个数的最大公约数的时候可以用欧几里得算法。
在欧几里得算法中,通过辗转相除,当余数为0的时候最后的除数就是两个数的最大公约数。
而在其扩展算法中,我们已知两个数的最大公约数,我们已知 a
x = 1(mod p),
展开就是 a x mod p = 1,首先我们先求 p = x1 * a + p1,然后p = a,a = p1,迭代下去
知道pi = 1(i表示出了i次)为之,然后就可以得出 1 = p - xi * a,此时的a和p已经不是我们初始的a和p了,我们需要往前推,推到 1= y
p + x*a 为止,此时得出的x就是a的逆元,当然如果逆元x为负数,或者比p大,要对其就行取余操作。
举个例子 11 = 1(mod 20)求11的逆元
20 = 1 * 11 + 9 //注释:此时x1 = 1, a = 11,p = 20,p1 = 9,执行p = a,a = p1
11 = 1 * 9 + 2 //注释:x2 = 1,a = 9,p2 = 2。
9 = 2 * 4 + 1 //注释:p3 = 1,
1 = 9-2 * 4。
从上述式子中可以得知 9 = 20-11
1 = 20-11-2 * 4
同时 2 = 11 -9
1 = 20 -11 -4 * (11-9)
已知 9 = 20 - 11
1 = 20 -11 -4 * (11-(20-11))
1 = 20 -11 -4 * (11-20+11)
合并同类项得
1 = 5 * 20 - 9 * 11
1 = y * 20 + x * 11
x为a的逆元 x = -9
x对p取余,x = 11
验证 11 * 11 = 121,121 mod 20 = 6 --- 1
到此 计算结束

求逆元的四种算法(拓欧费马小线性推欧拉)

求逆元的四种算法

拓展欧几里得算法求逆元

上一篇博客中已经讲过拓展欧几里得算法,并且讲解了求逆元的原理。这里只列出代码

在要求逆元的数与p互质时使用

代码

//扩展欧几里得定理
int ex_gcd(int a,int b,int& x,int& y)

    if(b==0)
    
        x=1;
        y=0;
        return a;
    
    int ans = ex_gcd(b,a%b,x,y);
    int tmp = x;
    x = y;
    y = tmp-a/b*y;
    return ans;

int cal(int a,int m)

    int x,y;
    int gcd = ex_gcd(a,m,x,y);
    //cout << "a " << a << " m " << m << " x " << x << " y " << y << endl;
    if(1%gcd!=0) return -1;
    x*=1/gcd;
    m = abs(m);
    int ans = x%m;
    if(ans<=0) ans += m;
    return ans;

费马小定理求逆元

在p是素数的情况下,有\\(a^p-1\\equiv1(\\mod p)\\),即\\(a^p-2a\\equiv1(\\mod p)\\)。所以a模p的逆元是\\(a^p-2\\),可用快速幂求解。

代码

//费马小定理
long long q_pow(long long a,long long b,long long p)

    long long res = 1;
    while(b)
    
        if(b&1)
        
            res = (res*a)%p;
        
        a = (a*a)%p;
        b>>=1;
    
    return res;

long long inverse(long long a,long long p)

    return q_pow(a,p-2,p);

线性递推求逆元

在p为质数且需要一次性打出[1,p-1]的所有逆元时可以使用

公式推导:现在求k的逆元

\\(ak+b=p\\)

\\(b*inv[b]\\equiv1\\mod p\\)

\\((p-ak)*inv[b]\\equiv1\\mod p\\)

\\((p*inv[b]-ak*inv[b])\\equiv1\\mod p\\)

因为\\(p*inv[b]\\equiv0\\mod p\\)

\\(-ak*inv[b]\\equiv1\\mod p\\)

\\(b=p\\%k\\)

\\(-ak*inv[p\\%k]\\equiv1 mod p\\)

\\(ak+b=p,所以a=p/k(整除)\\)

\\(-(p/k)*inv[p\\%k]*k\\equiv1\\mod p\\)

所以有\\(inv[k]=-(p/k)*inv[p\\%k]\\)

使用的时候加上p去掉负号

代码

//线性递推
int inv[max_n];
void ksm(int p)

    inv[1] = 1;
    for(int i = 2;i<=p-1;i++)
    
        inv[i] = (p-p/i)*inv[p%i]%p;
    

欧拉定理求逆元

在p为非质数时使用

欧拉定理表明,a,p互质时,有\\(a^\\phi(p)\\equiv1(\\mod p)\\),则a模p的逆元为\\(a^\\phi(p)-1\\)。求出欧拉函数后可用快速幂求得逆元。

代码

原理是\\(\\phi(n)=n*\\prod_i=1^k(1-\\frac1factor[i])\\)。factor[i]表示n的因子

//欧拉函数
int phi(int x)

    int ans = x;
    for(int i = 2;i*i<=x;i++)
    
        if(x%i==0)
        
            ans = ans/i*(i-1);
            while(x%i==0) x/=i;
        
    
    if(x>1)
        ans=ans/x*(x-1);
    return ans;

还有一种欧拉筛法,批量求欧拉函数,类似于埃氏筛

代码

//欧拉筛法
int Phi[max_n];
void euler(int N)

    Phi[1] = 1;
    for(int i = 2;i<N;i++)
    
        if(!Phi[i])
        
            for(int j = i;j<N;j+=i)
            
                if(!Phi[j])
                
                    Phi[j] = j;
                
                Phi[j] = Phi[j]/i*(i-1);
            
        
    

另一种更高效的筛法

原理:

p为质数,\\(\\phi(p)=p-1\\)

if\\(i\\%p==0\\),then \\(\\phi(i*p)=\\phi(i)*p\\)

if \\(i\\%p!=0\\),then \\(\\phi(i*p)=\\phi(i)*(p-1)\\).

代码

//更快的欧拉筛
int tot = 0;//质数个数
int prime[max_n];//质数
void Euler(int N)

    Phi[1] = 1;
    for(int i = 2;i<N;i++)
    
        if(!Phi[i])
        
            Phi[i] = i-1;
            prime[tot++] = i;
        
        for(int j = 0;j<tot&&(long long)i*prime[j]<N;j++)
        
            if(i%prime[j])
            
                Phi[i*prime[j]] = Phi[i]*(prime[j]-1);
            
            else
            
                Phi[i*prime[j]] = Phi[i]*prime[j];
                break;
            
        
    

还有一种似乎表面上原理不同,但实现与上面相似的欧拉筛,这次是直接求出[1,p-1]逆元的那种。

逆元有个性质:

\\(inv[a]*inv[b]=inv[a*b]\\)

因为\\(a*inv[a]\\equiv b*inv[b]\\equiv1(\\mod p)\\).

\\(ab*inv[a]*inv[b]\\equiv1(\\mod p)\\),

\\(inv[a*b]=inv[a]*inv[b]\\).

所以对于每个合数 ,我们把所有它的因子的逆元筛出来再相乘即可。

所以我们可以直接把所有素数筛出来,对它们求逆元(拓欧或费马小定理),再把它的逆元乘给它的倍数就可以了。

//另一种欧拉筛
int vis[max_n];
int inv[max_n];
int prime[max_n];//prime[0]记录质数个数
void EUler(int p)

    vis[1] = 1;
    inv[1] = 1;
    for(int i = 2;i<=p-1;i++)
    
        if(!vis[i])
        
            prime[++prime[0]] = i;
            inv[i] = q_pow(i,p-2,p);
        
        for(int j=0;j<prime[0];j++)
        
            if(i*prime[j]>p) break;
            inv[i*prime[j]]=inv[i]*inv[prime[j]];
            if(i%prime[j]) break;
        
    

写着写着老是有一种“回字的四种写法”的感觉ε=(′ο`*)))

参考文章

x义x的博客,【x义x讲坛】浅谈模质数意义下的乘法逆元,https://www.luogu.org/blog/zyxxs/post-xiao-yi-jiang-tan-qian-tan-sheng-fa-ni-yuan#

镜外,ACM数论之旅7---欧拉函数的证明及代码实现(我会证明都是骗人的╮( ̄▽ ̄)╭),https://www.cnblogs.com/linyujun/p/5194170.html

Rain722,乘法逆元的几种计算方法,https://blog.csdn.net/Rain722/article/details/53170288

hehe_54321,乘法逆元(欧拉函数,欧拉定理,质数筛法),https://www.cnblogs.com/hehe54321/p/7778955.html

以上是关于利用扩展的欧几里得算法求逆元的主要内容,如果未能解决你的问题,请参考以下文章

暑期学习日记二—利用扩展欧几里得求逆元

素数定理-欧几里得算法-乘法逆元

求逆元的四种算法(拓欧费马小线性推欧拉)

【总结】逆元的求法

求逆元

HDU-1576 A/B (扩展欧几里得求逆元)

(c)2006-2024 SYSTEM All Rights Reserved IT常识