先来引入求余概念 :
(a + b) % p = (a%p + b%p) %p (对)
(a - b) % p = (a%p - b%p) %p (对)
(a * b) % p = (a%p * b%p) %p (对)
(a / b) % p = (a%p / b%p) %p (错)
除法的是错误的,举一个例子就可以明白了:(6/2)%4=3,(6%4)/(2%4)=2/2=1
所以,(a / b) % p = (a%p / b%p) %p 是错的。
那么,当遇到在求余过程中遇到除法怎么办???这就体现出逆元的重要性了。
如果 ax = 1;那么 x 是 a 的倒数, x = 1/a;
但是a如果不是1,那么x就是小数。
那数论中,大部分情况都有求余,所以现在问题变了
ax = 1 (mod p)
那么x一定等于1/a吗???
当然不一定!!!!!
所以这时候,就把x看成a的倒数,只不过加了一个求余条件,所以x叫做: a关于p的逆元。
比如2 * 3 % 5 = 1,那么3就是2关于5的逆元,或者说2和3关于5互为逆元
这里3的效果是不是跟1/2的效果一样,所以才叫数论倒数(逆元)
a的逆元,用 inv(a)来表示
那么 (a / b) % p = (a * inv(b) ) % p = (a % p * inv(b) % p) % p
这样就把除法,完全转换为乘法了
下面介绍几种逆元的求法:
// 求解得到的 x 就是 a 的逆元,y 就是 b 的逆元。
int exgcd(int a, int b, int &x, int &y)
{
if(b == 0)
{
x = 1;
y = 0;
return a;
}
int r = exgcd(b, a % b, x, y);
int t = x;
x = y;
y = t - a / b * y;
return r;
}
/*求解ax+by=gcd(a,b),亦即ax≡1(mod b)。函数返回值是a,b的最大公约数,而x即a的逆元。
注意a, b不能写反了。*/
帮你理解:
假设有方程 ax + by = 1。由exgcd我们知道,如果ab互质,则方程有解。
那么这个解出来的x就是a关于b的逆元,y就是b关于a的逆元。
给方程两边同时对b求余:
a * x % b + b * y % b = 1 % b (b * y % b =0)
变成 a*x % b = 1 % b
转换个表示形式 a * x ≡ 1(mod b)
此方法求逆元时,一定要符合要求,即:ax + by = 1 且 gcd ( a, b) = 1 (a, b 互质),这样带入函数求出的结果才对应。
所以x就是a关于b的逆元,同理可证y。
// 要求 mod 为质数
long long quick_pow(long long a,long long b, long long mod) // a 为底,b 为指数
{
long long ans = 1; // ans 存储结果
long long base = a % mod; // base 为底
while(b != 0)
{
if(b & 1) //如果 b 为奇数
ans = ans * base % mod; //先乘以一个底,把指数变成偶数
base = base * base % mod; //指数消去 2
b >>= 1; //指数向右移动一位,即除以2
}
return ans;
}
long long fermat (long long a, long long mod)
{
return quick_pow(a, mod - 2, mod);
}
由费马小定理: a^(p-1) ≡1 (mod p) (这里 p 要求是一个质数,原始式子是 a^p ≡ a (mod p))
两边同除以a,得到:
a ^ (p - 2) ≡ 1 / a (mod p)
把1 / a 表示成逆元:a ^ (p-2) ≡ inv(a) (mod p)
得到:inv(a) = a ^ (p-2) (mod p)
用快速幂求一下,复杂度O(logn)。
// 求 n 以内所有数的逆元,但是 p 必须是奇质数.
int inv[N];
void get_inverse(int n, int p) {
inv[1] = 1;
for (int i = 2; i <= n; ++i) {
inv[i] = (p - p / i) * inv[p % i] % p;
}
}
这个算法可以在O(n)的时间复杂度内求出 n 以内所有数的逆元,不过需要注意的是p必须为奇质数。
这是网上的推导,解释起来有点麻烦。先用用就行了吧!!以后再慢慢理解。
最后,这里有一个求解逆元方法总结的网站:https://syncshinee.github.io/2015/05/10/InverseElement/