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

求逆元的四种算法

拓展欧几里得算法求逆元

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

在要求逆元的数与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-2}a\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-\frac{1}{factor[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

原文地址:https://www.cnblogs.com/zhanhonhao/p/11330792.html

时间: 2024-10-10 16:02:25

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

费马小定理及欧拉函数

2016.1.26 对于m=p1e1 . p2e2 . p3e3 . …… . pnen 欧拉函数定义为φ(m)=m * ∏(pi – 1)/pi 其意义为不超过m并且和m互素的数的个数 特别的φ(1)=1 对于和m互素的x,有xφ(m)≡1(mod m) 特别的,当p为素数时,x无法被p整除,φ(p)=p-1,于是便有费马小定理Xp-1≡1(mod p) 在p是素数时,对任意正整数x都有Xp≡X(mod p) 于是对于a的逆元x,有ax≡1(mod m),对于a,m互素时,有x=am-2,于是

CodeForces 300C Beautiful Numbers(乘法逆元/费马小定理+组合数公式+快速幂)

C. Beautiful Numbers time limit per test 2 seconds memory limit per test 256 megabytes input standard input output standard output Vitaly is a very weird man. He's got two favorite digits a and b. Vitaly calls a positive integer good, if the decimal

除法求模中求逆元的两种方法

今天下午还是有点闲的,不想刷题,不想补题,突然想起昨天的training 3里I题涉及到除法取模的问题,就来总结一下 首先对于模运算来说,是没有对于除法的取模的(即没有(a/b)%mod==a%mod/b%mod),但是在很多题目中都涉及到除法取模,所以就必须要了解或者掌握,对于除法取模以(a/b)%mod来说,我们首先需要得到b的逆元,根据逆元的定理 对于正整数和,如果有,那么把这个同余方程中的最小正整数解叫做模的逆元. 然后就是求逆元的两种方法. 第一种方法就是比较普遍的,也是挺基础的,就是

求平均数的四种方法

用c语言求平均数有四种方法,各有利弊.1.第一种是最常规的做法例 > int main() { int a = 10; int b = 5; c = (a + b) / 2; system("pause"); return 0; } 这种方法有缺陷,如果a和b足够大,以致于两数之和超出了intmax(整型所能代表的最大值),所以不建议使用这种方法.2.最常用的方法例 > int main() { int a = 10; int b = 5; c = a+(b-a)/2; s

在一个字符串中,统计大写字母个数,小写字母个数,其他字符个数的四种算法

题目描述:编写程序,输出字符串中的大写字母.小写小母和其他的个数.如有一个字符串"Helle, This is A test textfile.123456, tannk you!!",则其大写字母个数:3,小写字母个数:29,其他字符个数:18. 这里提供了四种算法,第一种是我们比较好理解的,也属于硬编码问题,其他三种方法要借助JAVA语言的jdk提供的api. 方法一: <!DOCTYPE html> <html lang="en"> &

(数论)简单总结求逆元的几种方法

逆元(Inverse element),如a?b≡1(modp),那么a,b互为模p意义下的逆元,则p|(a/c-b*c)(即a/c与b*c同余). 常用的求逆元方法有 1.费马小定理 若p为素数,且gcd(a,p)=1,则a^(p-1)≡1(mod p),即a*a^(p-2)≡1(mod p),故a的逆元为a^p-2. 2.拓展欧几里德算法(递推再回溯) 当gcd(A,B)|C时,可求二元一次方程Ax+By=C的整数通解. 3.逆元线性筛 递推公式:inv[i]=inv[p % i] * (p

【日常学习】乘法逆元&amp;&amp;欧拉定理&amp;&amp;费马小定理&amp;&amp;欧拉函数应用&amp;&amp;常大学霸

转载请注明出处 [ametake版权所有]http://blog.csdn.net/ametake欢迎来看看 今天花了一个多小时终于把乘法逆元捣鼓明白了 鉴于我拙计的智商抓紧把这些记录下来 在此本栏目鸣谢里奥姑娘和热心网友himdd的帮助和支持 那么正文开始··· 逆元是干什么的呢? 因为(a/b)mod p ≠(a mod p)/(b mod p) 我们需要想一种方法避免高精 那就是把除法转化为乘法 因为(a*b) mod p = ( a mod p ) *( b mod p ) 怎么转化呢?

HDU - 1576(费马小定理求逆元)

题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=1576 A/B Time Limit: 1000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)Total Submission(s): 9278    Accepted Submission(s): 7452 Problem Description 要求(A/B)%9973,但由于A很大,我们只给出n(n=A%99

费马小定理 求乘法逆元

//P3811 [模板]乘法逆元 #include<bits/stdc++.h> using namespace std; inline void write(long long X) { if(X<0) {X=~(X-1); putchar('-');} if(X>9) write(X/10); putchar(X%10+'0'); } long long qpow(long long n,long long m,long long mod) { long long ans=1;