1,定义
我们取余算法是没有除法取余的操作的(a/b)%p!=(a%mod/b%mod)%p.
因此我们乘法逆元的目的就是寻找一个整数x,使得(ax)%p等价于(a*1/b)%p.
我们称之为全等于≡,即ax=a*1/b(mod p).我们这个x的意义就与1/b一样。(即xb≡1(mod p))。
我们一般有3种求逆元的方法:费马小定理,拓欧,线性dp。
2,费马小定理求逆元
费马小定理:如果p为素数(这是前提)并且gcd(a,p)=1,那么a^(p-1)≡1(mod p)---->a^(p-2)*a≡1(mod p),所以a^(p-2)就是a的逆元。
证明如下:
1,因为p是素数,毫无疑问:a[1]=1~a[p-1]=p-1都与p互质。我们称a[1]~a[p-1]是p的一个完全剩余系。因为a,p互质。所以a*a[j]%p得到的数也肯定是1到p-1之间(不可能是0)。
2,我们保证容易 a*a[i] !≡ a*a[j] (mod p) . (i!=j)
证明如下:假设两边全等于,那么(a[i]-a[j])*a≡0(mod p).因为a,p互质,毫无疑问可以消去 a,最后变为a[i]≡a[j](mod p),显然不成立。
所以我们得出a*a[1]~a*a[p-1]也是p的一个完全剩余系。那么 a[1]*a[2]*....*a[p-1] ≡(a*a[1])*...* (a*a[p-1]) (mod p)消去a[1]*..*a[p-1],就得到a^(p-1)≡1(mod p).
3,所以如果给定的a,p互质且p为质素,我们可以直接用快速求a^(p-2)(mod p)就是a的逆元了。
ll a, mod;
ll fastmi(ll base, ll p)
{
ll ans = 1;
while (p)
{
if (p & 1)ans = (ans * base) % mod;
p >>= 1;
base = (base * base) % mod;
}
return ans;
}
int32_t main()
{
cin >> a >> mod;
cout << fastmi(a, mod - 2) << endl;//逆元是a^(p-2) (mod p)
return 0;
}
3,拓展欧几里得(exgcd)
先说求求逆元的应用:当gcd(a,p)=1,不要求p为素数,a的逆元就是ax+by=1的x。
-
我们首先证明ax+by=c有解的前提是c是gcd(a,b)整数倍 (裴蜀定理/贝祖定理)
还是反证法:假设c不是d=gcd(x,y)的整数倍,设ax=x*k1*d,by=y*k2*d,c=kd+h(其中k1与k2互质且0<h<d)。 那么原式<==>xk1d+yk2d=kd+h,两边同除于d,得到整数==整数+分数(显然不成立)
-
求逆元ax≡1(mod p)我们可以看出ax+py≡1=gcd(a,p)(mod p),可以补上py显然是py%p=0,y为任意解(无所谓,我只要x)
-
拓展欧几里得就是求当gcd(a,b)=1时,ax+by的解x,y。
欧几里得算法(gcd)辗转最后就是gcd(a,b)=a,b=0,此时x=1,y=0.由最终确定的x,y我们是可以一步步退回辗转过的a,b对于的解x,y,那么我们需要知道辗转时x1,y1与x2,y2的关系。
-
ax1+by1==1==bx2+(a%b)y2,(因为a%b==a-(a/b)*b)),所以a*(x1-y2)+b(y1-x2+(a/b)*y2)==0 解得x1=y2,y1=x2-(a/b)*y2。
int a, mod;
void exgcd(int a, int b, int &x, int &y)//&直接修改值
{
if (!b)x = 1, y = 0;
else
{
exgcd(b, a % b, y, x);//这里替换回来后,x就继承了深层的y,y就继承深层的x,那么x不用修改,y再减去(A/B)*y2即-(a/b)*x即可
y -= (a / b) * x;
}
}
int32_t main()
{
cin >> a >> mod;
int x, y;
exgcd(a, mod, x, y);
x = ( x + mod) % mod;//保证逆元为正数
printf("%lld\n", x);
return 0;
}
4,线性dp
如果只是求单个数的逆元,显然上面两种方法是足够的,但是如果求1~n的逆元,上面的复杂度就是O(n*logn),有点小高。
- 当我们求i的逆元时,尝试使用i之前求出的逆元。
- 令k=p/i,j=p%i , 显然p=k*i+j。建立了i与p与其他i的关系。可以得出k*i+j≡0(mod p),我们的目标是化出i^(-1)的形式,所以我们两边同乘i^(-1),j^(-1),即k*j^(-1)+i^(-1)≡0(mod p)。即i^(-1)≡-[p/i]*(p%i)^(-1) (mod p)。
- 因为p%i<i,所以(p%i)的逆元我们已经求出
cin >> n >> mod;
inv[1] = 1;
for (int i = 2; i <= n; ++i)inv[i] = (mod - mod / i) * inv[mod % i] % mod;//(mod-mod/i)是为了保证逆元为正数,反正mod p加上p也不影响
模板题:P3811 【模板】乘法逆元
#include <bits/stdc++.h>
using namespace std;
#define ll long long
const int N = 3e6 + 10;
int n, mod;
ll inv[N];
int32_t main()
{
cin >> n >> mod;
inv[1] = 1;
for (int i = 2; i <= n; ++i)inv[i] = (mod - mod / i) * inv[mod % i] % mod;//(mod-mod/i)是为了保证逆元为正数,反正mod p加上p也不影响
for (int i = 1; i <= n; ++i)printf("%lld\n", inv[i]);//毒瘤题不让我用cout
return 0;
}
5,线性dp拓展
通过观察逆元,我们发现即使求的n个数不是连续的,我们也可以递推得出n个数各自的逆元。
假设我们n个数积一起,那他们的积逆元sv[n]<==>1/(a[1]*a[2]*...*a[n]),如果我们sv[n]*a[n],会发现得到积逆元sv[n-1]。
我们得出求逆元inv[i],那么只需要sv[i]*s[i-1](即i的积逆元*前i-1个数的积就可以得到。
因此,我们只需要求sv[n]就可以递推全部。
模板题:P5431 【模板】乘法逆元 2
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define int ll
const int N = 5e6 + 100;
int sv[N], s[N], a[N];
int n, mod, k, ki = 1,ans;
inline ll read(ll &x)
{
x = 0;
char ch = 0;
while (ch < '0' || ch > '9')ch = getchar();
while (ch >= '0' && ch <= '9')
{
x = x * 10 + ch - '0';
ch = getchar();
}
return x;
}
ll fastmi(ll base, ll power)
{
ll ans = 1;
while (power)
{
if (power & 1)ans = (ans * base) % mod;
power >>= 1;
base = (base * base) % mod;
}
return ans;
}
int32_t main()
{
read(n), read(mod), read(k);
s[0] = 1;
for (int i = 1; i <= n; ++i)read(a[i]), s[i] = s[i - 1] * a[i] % mod;//乘法记得mod
sv[n] = fastmi(s[n], mod - 2);
for (int i = n - 1; i >= 1; --i)sv[i] = sv[i + 1] * a[i + 1] % mod;//求积逆元
for (int i = 1; i <= n; ++i)
{
ki = (ki * k) % mod;
int inv = sv[i] * s[i - 1] % mod ;//这勾题卡快读就算了,还卡这种常数,开inv数组没有必要,而且还会mle,用inv暂时存储inv[i]即可
ans = (ans + inv * ki % mod) % mod;
}
cout << ans << endl;
return 0;
}