乘法逆元

来源:互联网 发布:淘宝托管软件是真的吗 编辑:程序博客网 时间:2024/06/05 16:17

一. 乘法逆元

1. 逆元

在群G中,aGaGs.t.aa=e,其中eG的单位元。

2. 乘法逆元

p为素数,记ab=a×bmodp
在群(N)中,aNaNs.t.aa=e=1
则称aa关于modp的逆元。

为了方便表示,且下面的内容都只涉及到相同的p,我们记a关于modp的逆元为inv[a]


二. 逆元的性质

【性质1】(存在唯一性)
对于aN,有且仅有一个as.t.aa1(modp)
证明:假设存在a′′也满足a×a′′1(modp)
则有a×aa×a′′(modp)
aa′′(modp)
矛盾。

【性质2】(完全积性函数)
a,bNinv[a]×inv[b]inv[a×b](modp)
证明:
a×inv[a]1(modp)b×inv[b]1(modp)
(a×b)×(inv[a]×inv[b])1×1=1(modp)
由【性质1】知(a×b)的逆元只有一个。
inv[a×b]inv[a]×inv[b](modp)

【性质3】inv[k](p/k)×inv[pmodk](modp)

证明:见后面求逆元的方法(5)线性递推的推导。

【性质4】a/ba×inv[b](modp)
证明:aa
aa×b×inv[b]
gcd(inv[b],p)=1
a/ba×inv[b]


三. 求乘法逆元

1. 问题

已知:ap
条件:p为素数
求:as.t.aa1(modp)

2. 常见方法

(1)枚举法

【方法】1p1枚举a的值,转为判定性问题,判断aa是否关于p1同余。

【复杂度分析】
求单个数的逆元:O(p)
0p1的逆元:O(p2)

【代码】

#include <cstdio>int a,p;int main(void){    scanf("%d%d",&a,&p);    for (int _a=1;_a<p;_a++)        if (a*_a%p==1) {printf("inv[a]=%d\n",_a);return 0;}}

(2)快速幂

【方法】根据欧拉定理,aϕ(p)=1(modp)
ap11(modp)
a×a(p2)1(modp)
根据逆元的存在唯一性,inv[a]ap2(modp)
即可用快速幂求解。

【复杂度分析】
求单个数的逆元:O(logp)
1p1的逆元:O(plogp)

【代码】

#include <cstdio>int a,p;int Pow(int i,int j){    if (!j) return 1;    int now=Pow(i,j>>1);    now=now*now%p;    if (j&1) now=now*i%p;    return now;}int main(void){    scanf("%d%d",&a,&p);    printf("%d\n",Pow(a,p-2));    return 0;}

(3)扩展欧几里得算法

【方法】
要使得aa1(modp),我们只用找到ax+py=1,再使ax即可。
ax+py=1的找法就是扩展欧几里得算法。
我们对a,p最大公约数,顺便维护xy的值。
设当前我们的状态为(a,b)
①当b=0时,gcd(a,b)=a,所以x=1,y=0
②当b0时,我们先执行gcd(b,amodb)
现在有xb+y(amodb)=1
x×b+y×a(a/b)y×b=1
ya+[x(a/b)y]b=1
现在我们要构造x,y作为新的x,y满足ax+by=1
那么就可以使x=yy=x(a/b)y
最后的x即为所求。

【时间复杂度】
单个:O(logp)
所有:O(plogp)

【代码】

#include <cstdio>int a,p;int x,y;void exgcd(int a,int b){    if (!b) {x=1,y=0;return;}    exgcd(b,a%b);    int _x=x,_y=y;    x=_y,y=_y*(a/b)-_x;    x%=p,y%=p;}int main(void){    scanf("%d%d",&a,&p);    exgcd(a,p);    printf("%d\n",(x+p)%p);    return 0;}

(4)欧拉筛法

【方法】
注意这是积性函数!!!还是完全积性函数!!!
能不能用欧拉筛法求出所有的逆元!!!

a为素数
好像没事么特别的办法,直接用前面的方法2和3来解决,O(logp)

a为合数
完全积性函数O(1)解决。
甚至不用分有没有完全平方数因子的类。

【复杂度分析】
最初学习的时候,发现素数一次O(logp),好像还是要O(plogp)的时间求所有。

但是某天突然想起有个叫做素数定理的东西。
π(x)为小于x的素数的个数,则π(x)xlnx

那么求所有素数的复杂度为:
O(π(p)logp)=O(plogplnp)=O(p)
所有合数的复杂度为:O(p)
所以总的复杂度为O(p),总算又一个线性的逆元求法。

所以求所有逆元的时间复杂度为:O(p)

【代码】

#include <cstdio>const int N=100000;int p;int vis[N],pri[N];int inv[N];int mi(int i,int j){    if (!j) return 1;    int now=mi(i,j>>1);    now=now*now%p;    if (j&1) now=now*i%p;    return now;}int main(void){    scanf("%d",&p);    vis[1]=1,inv[1]=1;    for (int i=2;i<p;i++)    {        if (!vis[i]) pri[++pri[0]]=i,inv[i]=mi(i,p-2);        for (int j=1;j<=pri[0];j++)        {            if (i*pri[j]>=p) break;            inv[i*pri[j]]=inv[i]*inv[pri[j]];            if (i%pri[j]==0) break;        }    }    for (int i=1;i<p;i++)        printf("inv[%d] = %d\n",i,inv[i]);    return 0;}

(5)线性递推

【方法】
考虑能不能直接线性递推。
考虑能不能从小到大递推。

inv[1]=1
②假设我们已经求出了inv[i]i<k,现在求inv[k]
同余与整除、取模息息相关,又要利用起之前的结果。
不难想到取p=ak+b0b<k

现在可以利用的有p,a,b,inv[b]
b×inv[b]1(modp)
我们只用把b×inv[b]变形成k与由已知量组成的乘积,再根据存在唯一的逆元性质即可求出逆元。

b×inv[b](pak)×inv[b]ak×inv[b]=k×(a×inv[b])1(modp)

inv[k]a×inv[b](p/k)×inv[pmodk](modp)

这样就可以线性递推了啊。

【复杂度分析】
求所有逆元的复杂度为O(p)

【代码】

#include <cstdio>const int N=10000;int p;int inv[N];int main(void){    scanf("%d",&p);    inv[1]=1;    for (int i=2;i<p;i++)        inv[i]=(-(p/i)*inv[p%i]%p+p)%p;    for (int i=1;i<p;i++) printf("inv[%d] = %d\n",i,inv[i]);    return 0;}

3. 注意

(1)逆元的存在唯一?

注意,逆元的存在唯一性是在p为素数的条件下才成立的。

如果p不是素数,会怎么样?
只有(a,p)=1的数a才存在逆元,其余不存在!!!

(2)方法的比较

  • 较简单的题,可以使用效率较低的方法1求逆元。
  • 求少量数的逆元,或者模数很大,通常使用算法23。
  • 求大量数的逆元,通常使用算法45。
    个人推荐算法5,但是如果想不起的话可以写算法4。

四. 逆元的使用

原理:【性质4】a/ba×inv[b](modp)

使用情况1:当我们要求abmodp的值时,由于ab太大而无法直接计算,我们对a取模,但是a取模后不能除b,我们就要使用到b的逆元。
例题:【BZOJ】1004 Cards

使用情况2:当我们要求ab的值时,知道答案的范围ansk,但是ab太大无法直接计算。这时候我们就要假设出一个质数L>k,然后求abmodL即可。
这种方法好像目前没有考过,下次自己出一道2333。


0 0
原创粉丝点击