逆元有三种计算方法,分别是扩展欧几里得、费马小定理推论(快速幂求法)以及线性递推法。
众所周知,扩展欧几里得是求解二元一次方程的方法。因为逆元的定义为:如果a*b≡1(mod p),则:a、b在模p意义下互为逆元。
由此,可设k*p+1=a*b。
两边同减k*p,得:1=a*b-k*p。
因为正负没有关系,所以可以变为a*b+k*p=1。
因为我们知道a和p的值,所以可以把这个方程视为二元一次方程,可用扩展欧几里得求解。
#include
using namespace std;
#define pr pair
int Gcd(int a, int b) //根据裴蜀定理,判断是否有逆元
{
if(b == 0)
return a;
return Gcd(b, a % b);
}
pr Exgcd(int a, int b) //扩展欧几里得,得出的答案的第一项是逆元
{
if(b == 0)
return pr (1, 0);
pr t = Exgcd(b, a % b);
return pr (t.second, t.first - a / b * t.second);
}
int Inv(int a, int p)
{
pr t = Exgcd(a, p);
return (t.first % p + p) % p; //避免负逆元和逆元太大
}
int main()
{
int a, p;
scanf("%d %d", &a, &p);
if(Gcd(a, p) != 1)
printf("No inv!");
else
printf("%d", Inv(a, p));
return 0;
}
费马小定理:若a、p互质,则:a^p≡p(mod p)。
两边同除p,得:a^(p-1)≡1(mod p)。
左边拆分出一个a,得:a*a^(p-2)≡1(mod p)。
所以,a^(p-2)就是a的逆元,直接用快速幂求取即可。
2.代码:
#include
using namespace std;
int n;
long long fastpow(int x, int a, long long p)
{
long long t = x % p;
long long ans = 1;
while(a > 0)
{
if(a & 1)
ans = ans * t % p;
t = t * t % p;
a >>= 1;
}
return ans;
}
int main()
{
scanf("%d", &n);
while(n--)
{
int a, p;
scanf("%d %d", &a, &p);
if(a % p)
printf("%lld\n", fastpow(a, p - 2, p));
else
printf("impossible\n");
}
return 0;
}
已知i、模数p,求inv(i)。
设k=p/i(下取整),r=p%i。
所以k*i+r=p。
所以k*i+r≡0(mod p)。
两边同除(i*r),得:k/r+1/i≡0(mod p)。
所以k*inv(r)+inv(i)≡0(mod p)。
两边同减(k*inv(r)),得:inv(i)≡-k*inv(r)(mod p)。
又因为k=p/i,r=p%i,所以inv(i)≡(-p/i)*inv(p%i)(mod p)。
所以inv(i)≡(p-p/i)*inv(p%i)(mod p)。
又因为inv(i)*i≡1(mod p),所以(p-p/i)*inv(p%i)*i≡1(mod p)。
所以inv(i)=(p-p/i)*inv(p%i)。
注意最后的式子,我们可以看到:p%i比i小,因此可以把i从小到大枚举,就可以算出从0到maxi的每个数的逆元。因此,可以线性递推实现。
#include
using namespace std;
int n, p;
int inv[10000002];
int main()
{
scanf("%d %d", &n, &p);
inv[0] = inv[1] = 1; //初始化
for(int i = 2; i <= n; i++)
inv[i] = (long long)(p - p / i) * inv[p % i] % p; //强转型,避免溢出;取模,避免逆元太大
printf("%d\n", inv[n]);
return 0;
}