码迷,mamicode.com
首页 > 其他好文 > 详细

数论--逆元

时间:2017-10-05 20:55:15      阅读:127      评论:0      收藏:0      [点我收藏+]

标签:under   const   ++   scan   logs   def   快速幂   存在   扩展欧几里得   

首先,引入求余的概念:

(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,则 x=1/a 是a的倒数

类似,若 a*x=1(mod p) ,我们称x是a关于p的逆元,它的效果也是跟倒数1/a一样的,但x不一定等于1/a,(当然,a与p互质才存在逆元)

记a的逆元为inv(a)

比如:2*3=1(%5),称2与3关于5互为逆元

 

那么,问题来了,如何求逆元???

方法一:

费马小定理:a^(p-1) ≡1 (mod p)

也就是,a*a^(p-2)≡1(mod p)

看看结构是不是跟上面那个式子很像,也就是x=a^(p-2)=inv(a)

这样以来就能用求a^(p-2)来求a关于p的逆元inv(a)了,直接用我们前面介绍到的快速幂即可,时间复杂度是O(log n)

 1 LL pow_mod(LL a, LL b, LL p){//a的b次方求余p 
 2     LL ret = 1;
 3     while(b){
 4         if(b & 1) ret = (ret * a) % p;
 5         a = (a * a) % p;
 6         b >>= 1;
 7     }
 8     return ret;
 9 }
10 LL Fermat(LL a, LL p){//费马求a关于b的逆元 
11         return pow_mod(a, p-2, p);
12 }

 

方法二:

扩展欧几里得的方法

a*x + b*y = 1有解

两边同时 mod b, 

a*x%b+b*y%b=1%b

即a*x=1(%b),看,又出现了这个式子

即x是a关于b的逆元

同理,y是b关于a的逆元

代码:

 1 #include<cstdio>
 2 typedef long long LL;
 3 void ex_gcd(LL a, LL b, LL &x, LL &y, LL &d){
 4     if (!b) {d = a, x = 1, y = 0;}
 5     else{
 6         ex_gcd(b, a % b, y, x, d);
 7         y -= x * (a / b);
 8     }
 9 }
10 LL inv(LL t, LL p){//如果不存在,返回-1 
11     LL d, x, y;
12     ex_gcd(t, p, x, y, d);
13     return d == 1 ? (x % p + p) % p : -1;
14 }
15 int main(){
16     LL a, p;
17     while(~scanf("%lld%lld", &a, &p)){
18         printf("%lld\n", inv(a, p));
19     }
20 }

 

方法三:

inv(a) = (p - p / a) * inv(p % a) % p

证明如下:

设x = p % a,y = p / a
于是有 x + y * a = p
(x + y * a) % p = 0
移项得 x % p = (-y) * a % p
x * inv(a) % p = (-y) % p
inv(a) = (p - y) * inv(x) % p    //最后两步注意变换
于是 inv(a) = (p - p / a) * inv(p % a) % p

 于是可以通过递归来求inv(a),直到1为止,1的逆元是1

 1 #include<cstdio>
 2 typedef long long LL;
 3 LL inv(LL t, LL p) {//求t关于p的逆元,注意:t要小于p,最好传参前先把t%p一下 
 4     return t == 1 ? 1 : (p - p / t) * inv(p % t, p) % p;
 5 }
 6 int main(){
 7     LL a, p;
 8     while(~scanf("%lld%lld", &a, &p)){
 9         printf("%lld\n", inv(a%p, p));
10     }
11 }

这个方法不限于求单个逆元,比前两个好,它可以在O(n)的复杂度内算出n个数的逆元

递归就是上面的写法,加一个记忆性递归,就可以了

递推这么写

#include<cstdio>
const int N = 200000 + 5;
const int MOD = (int)1e9 + 7;
int inv[N];
int init(){
    inv[1] = 1;
    for(int i = 2; i < N; i ++){
        inv[i] = (MOD - MOD / i) * 1ll * inv[MOD % i] % MOD; //中间的1ll是把这个参数变成long long 类型的意思 
    }
}
int main(){
    init();
    for(int i=1;i<=10;i++)
    printf("%d ",inv[i]); 
}

 

数论--逆元

标签:under   const   ++   scan   logs   def   快速幂   存在   扩展欧几里得   

原文地址:http://www.cnblogs.com/eastblue/p/7629912.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!