乘法逆元

一、逆元定义

若a * x ≡ 1 (mod b),且a和b互质

那么就能定义:x为a的逆元,记为a-1

所以诚x为a在mod b的意义下的倒数

所以对于a/b(mod p)

就可以求b在mod p下的逆元,然后乘上a,再mod p。

 

二、适用范围

乘法逆元一般用于求  a/b (mod p)  的值

(p通常为质数,也可以不为质数)

 

三、求解逆元的四种方法

拓展欧几里得

费马小定理

线性递推

阶乘

 

注:

前两个算法(拓展欧几里得 和 费马小定理)适合求解单个值的逆元  [拓展欧几里得似乎比费马小定理还要好一些]

后两个算法(线性递推 和 阶乘)适合一串数对于一个mod p的逆元  [线性递推大概要比阶乘好一些吧]

 

四、算法

 

1.拓展欧几里得

适用范围:a和p互质 p可以不是质数

利用拓展欧几里得求解同余方程 a * x ≡ c (mod b) 当c = 1的情况

转化成解 a * x + b * y = 1 这个方程

 

#include<cstdio>
#include<algorithm>
#define ll long long
using namespace std;
ll n,p;
void exgcd(ll a,ll b,ll &x,ll &y)
{
    if(!b)
        x = 1,y = 0;
    else
        exgcd(b,a%b,y,x),y -= a/b * x;
}
int main()
{
    scanf("%lld%lld",&n,&p);
    ll x,y;
    for(int i = 1; i <= n; i++)
    {
        x = 0,y = 0;
        exgcd(i,p,x,y);
        x = (x % p + p)%p;
        printf("%lld\n",x);
    }
    return 0;
}

(按洛谷板子题写的)(tle两个点)

 

2.费马小定理

咕咕咕(自己的一篇极简题解)

适用范围:a和p互质,p为素数。

根据费马小定理

bp - 1 ≡ 1(mod p),即  b * bp - 2 ≡ 1 (mod p)

因此,当模数p为质数时,bp - 2即为b的乘法逆元

#include<cstdio>
#include<algorithm>
#define ll long long
using namespace std;
ll n,p;
ll qpow(ll a,ll b)
{
    a %= p;
    ll sum = 1;
    while(b)
    {
        if(b%2 == 0)
            a = (a * a)%p,b/=2;
        else
            sum = (sum * a)%p,b--;
    }
    return sum % p;
}
int main()
{
    scanf("%lld%lld",&n,&p);
    for(int i = 1; i <= n - 1; i++)
        printf("%lld\n",qpow(i,p - 2));
    printf("%lld",qpow(n,p - 2));
    return 0;
}

(tle了两个点)

 


3.线性递推

首先有一个,1-1 ≡ 1(mod p)

然后设p = k * i + r ( 1 < r < i < p)

也就是k是 p / i 的商,r是余数

再讲这个式子放到(mod p)意义下就会得到

k * i + r ≡ 0

然后乘上i-1 , r-1 就可以得到

k * r-1 + i-1 ≡ 0 (mod p)

 i-1 ≡ -k * r-1 (mod p)

i-1 ≡ -(p / i) * (p mod i)-1(mod p)

#include<cstdio>
#define ll long long
using namespace std;
int main()
{
    ll n,p;
    ll inv[3000005]; 
    scanf("%lld%lld",&n,&p);
    inv[1] = 1;
    for(ll i = 2;i <= n;i++)
        inv[i] = (p - p / i) * inv[p % i] % p;
    for(ll i = 1;i <= n;i++)
        printf("%lld\n",inv[i]);
    return 0;
} 


4.阶乘

inv[i + 1] = 1/ (i + 1)!

inv[i + 1] * (i + 1) = 1 / i! = inv[i]

所以我们可以求出n!的逆元,然后逆推,就可以求求出1...n!所有的逆元了

逆推式为

inv[ i + 1 ] * (i + 1)= inv[ i ]

所以我们可以求出任意i,i!,1/i! 的取值了

然后这个也可以导出 1 / i (mod p) 的取值

也就是 1 / i ! * ( i - 1) ! = 1 / i ( mod p )

 

#include<cstdio>
#define ll long long
using namespace std;
ll n,p;
ll inv[3000005],f[3000005];
ll qpow(ll a,ll b)
{
    ll sum = 1;
    while(b)
    {
        if(b%2 == 1)
            sum = (sum * a) % p , b--;
        else
            a = (a * a) % p , b /= 2;
    }
    return sum;
}
int main()
{
    scanf("%lld%lld",&n,&p);
    f[0] = 1; 
    for(int i = 1; i <= n; i++)
        f[i] = (f[i - 1] *i) %p;
    ll last = qpow(f[n],p - 2);
    ll k;
    for(ll i = n; i >= 1; i--)
    {
        k = (last * i) % p;
        inv[i] = last * f[i - 1] % p;
        last = k;
    }
    for(int i = 1;i <= n;i++)
        printf("%lld\n",inv[i]);
    return 0;
}

 

上一篇:牛客 197C 期望操作数


下一篇:CF451E Devu and Flowers