【模板】乘法逆元

  • 基本介绍
  • 模板题目
  • 代码实现

基本介绍

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

数论倒数 又称逆元 (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
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值