浅析逆元&逆元的蛋用(完全版)

来源:互联网 发布:52mac网可靠吗 编辑:程序博客网 时间:2024/05/17 04:49

  • 逆元
    • 某些性质
    • 演算一下
    • 逆元的蛋用
  • 逆元这东东怎么求
    • 方法一
      • code
    • 方法二
      • code
    • 方法三
      • 递归code
      • 递推code
  • said at the end

逆元

首先扯一点没有多大用的东西
在数论里面,我们通常不把倒数叫做倒数,而叫做逆元(纯属装13)
逆元的作用是非常非常大的,下面我们来看点easy的栗子


某些性质

(a+b)amodp+bmodp(modp)

(ab)amodpbmodp(modp)

(a×b)amodp×bmodp(modp)

前面三个都是对的(不需要证明~) 但是——

(a÷b)modp((amodp)÷(bmodp))modp

上面的÷是整除

不等号左面是等于6的,而不等号右面是等于2的,明显不相等


演算一下

这是不是说我们对除法下的大数模操作就毫无办法了?答案是no
为了方便,先来看看一些小学or初中级别的东东

比如说有条等式

ax=1
明显x=1a,没毛病

然后我再加一个条件

ax1(modp)
那么现在x就不一定等于1a
对于①式,我们就把x看成1a,而②式加了一个同余p的条件(mod p)
这时候,我们不把x看成倒数。这时的x为a关于p的逆元
(注意,当且仅当(a,p)=1且p为质数时,x才存在)

又是一个栗子

(3×5)mod7=1
3×51(mod7)

在这里,5和13的作用是一样的(乘3再模7都是1),所以,5是3关于7的逆元
(注意一点,除了α=5,没有其他整数α满足3α≡1(mod 7),可以手玩试试,所以说逆元是唯一的)


逆元的蛋用

前面说了一些,相信逆元的定义了应该很好懂
我们不妨把x的逆元用inv(x)来表示,那么对于除法——

(a÷b)modp=(a×inv(b))modp=(amodp×inv(b)modp)modp

难搞的除法瞬间变成了乘法,秒变水了很多


逆元这东东怎么求


方法一

基础数论里面有两条著名的定理:欧拉定理费马小定理
欧拉定理:(当(a,n)=1时成立)
费马小定理:(当(a,p)=1且p为质数时成立)
(我难道会告诉你我两个都不会证明吗……)

很明显,费马小定理是可以从欧拉定理推过来的
当p为质数时φ(p)=p1,代入欧拉定理即为费马小定理

既然我们在谈论逆元,就把费马小定理两边同除以一个p

ap21/a(modp)

attention to到一点就是1a是个小数,而等号左边是个整数,别忘了我们在说的是数论倒数
这时拍拍自己的脑袋,看一看上面,我们突然发现应该把1a写成inv(a)——
所以——
inv(a)ap2(modp)

我们可以用快速幂来求a的逆元,时间复杂度O(log2p)

code

long long inv(long long a,long long p){    long long t=1,b=p-2;    while (b)    {        if ((b%1)==1)t=t*a%p;        a=a*a%p;        b/=2;    }    return t;}

通常题目会让你求10^9+7取模,这方法可以说快到飞起
方法一用快速幂求逆元,容易上手,对于OI大多数的题目,已经足够


方法二

逆元还可以用扩展欧几里得算法来求,怎么求?一步一步来
首先要知道一个概念:贝祖定理,他的描述是

对于a,b>0且a,b∈N,必然存在整数x和y,使得ax+by=gcd(a,b)

比如我们现在要求a关于p的逆元(条件是a和p互质且p是质数)
代入贝祖定理,则

ax+py=1
(因为a和p互质所以(a,p)=1)

给上面那个式子做点操作,两边各模一个p,得到

axmodp+pymodp=1modp
axmodp=1modp
ax1(modp)

∴x是a关于p的逆元(其实y也是p关于a的逆元,可证,同理)
这里用扩展欧几里得exgcd(a,p,x,y),求出的x即为a的逆元

code

void exgcd(long long a,long long b,long long &x,long long &y){    if (b==0)     {        x=1;        y=0;        return;    }    exgcd(b,a%b,y,x,d);    y-=x*(a/b);}long long inv(long long a,long long b){     long long x,y;     exgcd(a,b,x,y);     return (x%p+p)%p;}

扩展欧几里得可谓比较牛,优点是用途多,能求gcd能求逆元能解线性方程
对于广大OIers是把利器,学会是必须的


方法三

最后一种方法更简单更好打更好理解:递归
先来一发证明(网上找的)

x=pmoday=p÷a
(x+ay)modp=pmodp=0
xmodp=aymodp
xinv(a)modp=(y)modp
inv(a)=(py)inv(x)modp
inv(a)=(pp÷a)inv(pmoda)modp

有了这个等式,我们就可以用递归来求inv(a)了
递归出口inv(1)=1,因为1的逆元就是1

递归code

long long inv(long long a,long long p) //注意a要小于p,先把a%p一下 {    return a==1?1:(p-p/a)*inv(p%a,p)%p;}

是不是短到爆炸?

因为pmoda后至多是12p,所以时间复杂度是O(log2p)
这种思想比费马小定理、扩展欧几里得优的地方是,他能用O(n)的时间预处理1~n的逆元
只不过把递归的式子改成递推而已

递推code

void init(){    inv[1]=1;    for(int i=2;i<=n;i++)    {        inv[i]=(p-p/i)*1ll*inv[p%i]%p;    }}

这是方法三,个人感觉方法三在某些题目里更好用一些
易理解,码量低,时间复杂度也低,还能快速预处理,interesting——


said at the end

三种求逆元的方法,各有各自的优点

  • 方法一,适合不太会证明但很会打代码的coder

  • 方法二,数学思维强且脑子转得快的小牛用吧

  • 方法三则属于比较懒、懒得打键盘的OIers

无论哪种方法,能求逆元的方法都是好方法

又涨了不少姿势
thanks for your watching!

原创粉丝点击