【模板】乘法逆元

来源:互联网 发布:液压制图软件 编辑:程序博客网 时间:2024/05/22 05:15
  • 基本介绍
  • 模板题目
  • 代码实现

基本介绍

先安利一个博客 非常好 镜外之主

数论倒数 又称逆元 (a的倒数在数论中不是1/a)
我们知道 下面这三个是对的
(a + b) % p = (a%p + b%p) %p
(a - b) % p = (a%p - b%p) %p
(a * b) % p = (a%p * b%p) %p
但是这个 不行
(a / b) % p = (a%p / b%p) %p
对于一些题目 我们必须在中间过程中进行求余 那如果这个算式中出现除法 我们该怎么办 于是引入逆元

在这个式子a*x = 1 (mod p)中 x一定等于1/a吗?
不一定 但是x确实a的倒数 只不过加了一个求余条件 所以x叫做 a关于p的逆元 用inv(a)来表示

比如2 * 3 % 5 = 1 那么3就是2关于5的逆元 或者说2和3关于5互为逆元 这里3的效果如同1/2 所以叫数论倒数
那么我们就可以把除法转换为乘法(乘逆元)

求逆元的方法见代码实现
我会的是费马 扩展欧几里得 和线性计算

模板题目

题目描述
给定n,p求1~n中所有整数在模p意义下的乘法逆元。

输入输出格式
输入格式:
一行n,p
输出格式:
n行,第i行表示i在模p意义下的逆元。

输入输出样例
输入样例:
10 13
输出样例:
1
7
9
10
8
11
2
5
3
4
说明
输入保证p为质数。

代码实现

  • 费小马定理

a(p1)1modp

a(p2)inv(a)modp

inv(a)=a(p2)modp

所以用上快速幂就行了
代码如下

        ll n , p;inline ll pow(ll a , ll b , ll p){        ll ans = 1;        while(b){                if(b & 1)   ans = (ans*a)%p;                a = (a*a)%p;                b >>= 1;        }        return ans;}inline ll fermat(ll x , ll p){        return pow(x , p - 2 , p);}int main(){        n in;   p in;        for(register int i=1;i<=n;i++)                printf("%d\n" , fermat(i , p));        return 0;}

这个比较慢

  • 扩展欧几里得

ax+by=1
如果ab互质,有解
这个解的x就是a关于b的逆元
y就是b关于a的逆元
我们可以在等号两边同时取模a或者b证明

但是我真的看不大懂代码 背吧
代码如下

        ll n , p;        ll x , y , d;void exgcd(ll a , ll b , ll &x , ll &y , ll &d){        if(!b){                d = a;  x = 1;  y = 0;        }        else{                exgcd(b , a%b , y , x , d);                y -= x*(a/b);        }}inline ll inv(ll x , ll p){        exgcd(x , p , x , y , d);        return d == 1 ? (x%p + p)%p : -1;}int main(){        n in;   p in;        for(register int i=1;i<=n;i++)                printf("%d\n" , inv(i , p));}

这个比费马快一点 但也不是最快的

  • 线性

推一下:

pmoda+p/aa=p

(pmoda+p/aa)modp=0

(pmoda)modp=(p/aa)modp

(pmoda)inv(a)modp=(p/a)modp

inv(a)=(pp/a)inv(pmoda)modp

得到一个式子 我们可以递归搞
代码如下

        ll n , p;ll inv(ll x , ll p){        return x == 1 ? 1 : (p - p/x)*inv(p%x , p)%p;}int main(){        n in;   p in;        for(register int i=1;i<=n;i++)                printf("%d\n" , inv(i , p));}

但这样其实比费马还慢
所以我们建一个inv[size]来存结果 就不用多次递归浪费时间
代码如下

#include<iostream>#include<cstdio>#include<cctype>    using namespace std;    #define in = read();    typedef long long ll;    typedef unsigned int ui;    const ll size = 5000000 + 10000;\        ll n , p;        ll inv[size];inline ll read(){        ll num = 0 , f = 1;    char ch = getchar();        while(!isdigit(ch)){                if(ch == '-')   f = -1;                ch = getchar();        }        while(isdigit(ch)){                num = num*10 + ch - '0';                ch = getchar();        }        return num*f;}int main(){        n in;   p in;        inv[1] = 1;     printf("1\n");        for(register int i=2;i<=n;i++){                inv[i] = (p - p/i)*inv[p%i]%p;                printf("%d\n" , inv[i]);        }}//COYG