逆元:ax≡1(mod p)当a和p互质时,方程的解 x 称为a关于p的逆元,
在普通的四则运算中,只有加减乘三种运算可以根进行分别取余运算,因为这三种运算都是从低位到高位的运算,而对于除法是从高位到低位的运算,显然不能直接进行取余,这时候,就要用到逆元有关的运算。
逆元可以近似的看作倒数的概念
例如,
如果要求(x / y)%p ,显然不可以(x%p)/(y%p),
利用逆元运算:可以将(x / y)%p化为 (x * Y )%p ,其中Y是y关于p的逆元
那么怎么求Y呢:
由逆元的定义,有yY≡1(mod p),
费马小定理:如果p是一个质数,而整数a不是p的倍数,则有a^(p-1)≡ 1(mod p)。
我们分离一项出来 a • a^(p-2) ≡ 1(mod p).
对比逆元的方程式,可以很容易得到,a关于p的逆元就是 a^(p-2),那么Y==y^(p-2)。
证明转化过程:
费马小定理: y • y^(p-2) % p ==1
两边乘以x/y: x • y^(p-2) % p == x / y
注意以上所有的的运算和推导过程都要在一个前提条件下: x和p互质,即gcd(x,p)==1
有了以上理论基础,就可以很容易实现了
1、快速幂取模版
#include
#define lom long long
using namespace std;
lom quick(lom a,lom b,lom c)//快速幂取模
{
lom ans=1;
a%=c;
while(b)
{
if(b&1) ans=ans*a%c;
a=a*a%c;
b>>=1;
}
return ans%c;
}
lom divi(lom a,lom b,lom p)
{
b=quick(b,p-2,p); //b的逆元
return a*b%p;
}
2、递推版
证明:
们要求i在模p意义下的逆元inv[i],那么我们就设ki+r=p,所以ki+r ≡ 0(mod p)。
移项可以得到:r ≡ -ki (mod p)。
两边同时除以ir,就可以得到这个式子:
那么i分之一就是i在模p意义下的逆元,r分之一就是r在模p意义下的逆元。
变成这个式子:inv[i] ≡ -k*inv[r](mod p)。
因为r
右边加上一个p:inv[i]=p-k*inv[p%i],k化为p/i(向下取整了所以不考虑r),最后inv[i]就化为了p-p/i*inv[p%i],就是上面的递推式。
#include
#define M 50000
#define lom long long
using namespace std;
const int mod=17;
int inv[M];
void getinv()
{
inv[1]=1;
for(int i=2;i<=M;i++)
inv[i]=mod-(lom)mod/i*inv[mod%i]%mod;
}
阶乘逆元, finv[i] 存储的是i的阶乘的逆元,一般用于组合数
void getinv()
{
finv[0] = finv[1] = f[0] = f[1] = inv[0] = inv[1] = 1;
for(int i=2; i <= M; i++)
{
f[i] = f[i-1] * i % mod;
inv[i]=mod - mod / i * inv[mod%i] % mod;
finv[i] = finv[i-1] * inv[i] % mod;
}
}
以上求逆元的方法有一个限制条件就是互素
但如果满足 y|x(x%y==0),就可以忽略互素的条件
ans==(x/y)%p == a%(p*y)/b
证明:
设: x/y == k*p + t
x == k*p*y + t*y
x%(p*y)== t*y
x%(p*y)/y == t
所以t == (x/y)%p == x%(p*y)/y