乘法逆元
1.使用情况:
用法1.在求解除法取模问题(a/b)%m时,我们可以转化为(a%(b∗m))/b,
但是如果b很大,则会出现爆精度问题,所以我们避免使用除法直接计算。
用法2.当我们要求(a/b) mod m的值,且a很大,无法直接求得a/b的值时,我们就要用到乘法逆元。
我们可以通过求b关于p的乘法逆元k,将a乘上k再模p,即(a*k) mod m。其结果与(a/b) mod m等价。
遇到上面的情况,我们可以使用逆元将除法转换为乘法:
假设b存在乘法逆元,即与m互质(充要条件)。设c是b的逆元,即b∗c≡1(modm),
那么有a/b=(a/b)∗1=(a/b)∗b∗c=a∗c(modm)
即,除以一个数取模等于乘以这个数的逆元取模。
2.方法:
- 逆元求解一般利用扩欧。
- 当m为质数的时候直接使用费马小定理,m非质数使用欧拉函数。
- 当m为质数的时候,神奇的线性方法。
3.代码:
#include<cstdio> #include <math.h> using namespace std; typedef long long ll; const int p = 1e9 + 7; const int N = 1e5 + 5; ll inv[N]; ll inv1(ll a)//递推法求逆元 { return a==1?1:(p-p/a)*inv1(p%a)%p; } void inv2(ll n)//通过递推进行逆元打表 { inv[1] = 1; for (ll i=2; i<=n; ++i) { inv[i] = (ll) (p - p / i) * inv[p%i] % p; } } void exgcd(ll a,ll b,ll& d,ll& x,ll& y) { if(!b) { d = a; x = 1; y = 0; } else { exgcd(b, a%b, d, y, x); y -= x*(a/b); } } ll inv3(ll a)//扩展欧几里得 { ll d, x, y; exgcd(a, p, d, x, y); return d == 1 ? (x+p)%p : -1; } ll inv4(ll a, ll b)//费马小定理 { ll res = 1; while(b) { if(b&1) res = (res*a)%p; b = b>>1; a = (a*a)%p; } return res; } int main() { ll b; while(1) { scanf("%lld",&b); ll ans1=inv1(b); printf("%lld\n",ans1); inv2(100);//100以内的数对P的逆元 ans1=inv[b]; printf("%lld\n",ans1); ans1=inv3(b); printf("%lld\n",ans1); ans1=inv4(b,p-2); printf("%lld\n",ans1); } }