-
引子
经过几天的努力,终于将逆元这个大难题给攻克了……心情激动的一时无法平复。下面就简单介绍一下关于逆元的知识,来记录自己的成果,也希望能够帮助到别人。某些地方可能理解的不够深入,还请多多包涵。
-
定义
逆元,又称数论倒数,如果a*x≡1 (mod p),且gcd(a,p)=1(a与p互质),则称a关于模p的乘法逆元为x。(来自维基百科)
怎么说呢,逆元其实就是一个相当于倒数的东西,只不过是多模了一个p而已。要说它有什么用,那么请回忆一下倒数有什么用吧。没错,在上小学时,老师就教导过我们,除以一个数,就是乘它的倒数。其实,逆元也是充当这个“倒数”这个角色。还记得吗,在模运算中:
(a+b) % p == ( a%p + b%p )% p;
(a-b) % p == (a%p - b%p + p) % p;(否则可能为负数)
(a×b) % P == (a%p) * (b%p) %p;
But
(a/b) % p != (a%p) / (b%p) % p 。!
所以呢,我们可以用到一个叫做逆元的东西:
(a / b) = ( a * inv(b))
so, (a / b) % p = (a%p) * (inv(b)%p) % p
这个式子可以配合lucas定理解决一系列问题。
其实逆元的用处还有很多,配合lucas定理只是其中一个。
由于他的意义和用处实在是太深奥了,作为一名蒟蒻,还不具备将它的全部用处解释出来的能力,对于用处就先写到这吧。
-
求解?
逆元的求法有很多种,介于本人实力有限,着重将以下三种解法:
1、费马小定理(Fermat‘s little theorem)
(又称费小马定理)
定理如下:假如是质数,且gcd(a,p)=1,那么有 a(p-1)≡1(mod p)(来自Wikipedia)
为什么说可以用它求呢?
a(p-1) ≡ 1(mod p) ----> a * a(p-2)≡1(mod p)
是不是和逆元的定义很像?
so,如果p是素数,那么a(p-2)就是a的逆元。
神奇吧!
关于a(p-2),我们可以用快速幂来求。
核心代码如下:
1 long long fp(long long n,long long k,long long p) 2 { 3 long long s = k,ans = 1,t = n; 4 while(s){ 5 if(s & 1) 6 ans *= t%p; 7 t *= t%p; 8 s >>= 1; 9 } 10 return ans % p; 11 } 12 long long inv(long long N,long long P) 13 { 14 return fp (N,P-2,P) % P; 15 }
第2种方法就是用拓展欧几里徳算法。
其描述如下:
对于a * x + b * y == gcd(a,b),若a,b互质,那么
ax + by == 1
将两边同时mod b得:
a * x ≡ 1 (mod b)
所以x是a关于b的逆元,亦可证明y是b关于a的逆元。
所以只要求出它的解来就行了
1 #include <iostream> 2 #include <cstdio> 3 #include <cmath> 4 using namespace std; 5 typedef long long ll; 6 ll n,p; 7 void Euc(ll a,ll b,ll& x,ll& y) 8 { 9 if(!b){ x = 1; y = 0; } 10 else{ Euc(b,a%b,y,x); y -= (a/b)*x; } 11 } 12 ll inv(ll N,ll P) 13 { 14 ll X,Y; 15 Euc(N,P,X,Y); 16 X = (X%P + P) % P; 17 return X; 18 } 19 int main() 20 { 21 scanf("%lld%lld",&n,&p); 22 for( int i = 1; i <= n; i++) 23 printf("%lld\n",inv(i,p)); 24 return 0; 25 }
至于拓欧的求法……
3、用递推来求:
有这麽一个等式:
inv(a) == (p - p / a) * inv(p % a) % p
证明:
待续。。。
所以。用递推很好解决:
1 #include <iostream> 2 #include <cstdio> 3 using namespace std; 4 typedef long long LL; 5 const int MAXN = 3000001; 6 LL n,p; 7 LL inv[MAXN]; 8 void Inv() 9 { 10 inv[1] = 1; 11 for( int i = 2; i <= n; i++) 12 inv[i] = (p - p/i) * (inv[p%i]%p) % p; 13 } 14 int main() 15 { 16 scanf("%lld%lld",&n,&p); 17 Inv(); 18 for( int i = 1; i <= n; i++) 19 printf("%lld\n",inv[i]); 20 return 0; 21 }
最后,不得不感叹一句:探索知识的道路真是千辛万苦啊……
——————————————————————————————— continue ————————————————————————————————————
原文地址:https://www.cnblogs.com/hnfms-jerry/p/8597310.html