数论倒数,又称逆元
先讲求余的概念:
(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 (错)
为什么除法是错的呢?
举个例子: (100/50) % 20 = 2 != (100%20)/(50%20) % 20 = 0
对一些题目,我们需要在中间进行求余运算,否则数据太大可能会爆!那么是不是这个算式中出现了除法,那么我们就无法运算了呢?
当然不是!!
这个时候就需要逆元了!
我们知道
如果
a*x = 1
那么x是a的倒数,x = 1/a
但是a如果不是1,那么x就是小数
那数论中,大部分情况都有求余,所以现在问题变了
a*x = 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和p互质,a才有关于p的逆元
那么(a/b)%p = (a * inv(b)) % p = (a % p * inv(b) % p) % p
逆元已经介绍清楚了,那么如何去求逆元呢?
方法一:
利用费马小定理
a^(p-1) ≡1 (mod p)
推导出:
inv(a) = a^(p-2) (mod p)
再运用下我们之前提过的快速幂!
注意一下有求inv(a) 的前提是a和p互质
LL pow_mod(LL a, LL b, LL p){//a的b次方求余p LL ret = 1; while(b){ if(b & 1) ret = (ret * a) % p; a = (a * a) % p; b >>= 1; } return ret; } LL Fermat(LL a, LL p){//费马求a关于b的逆元 return pow_mod(a, p-2, p); }
方法二:
利用扩展欧几里得
a*x + b*y = 1
如果a,b互质,有解
则这个解的x 就是 a关于b的逆元
y 就是 b关于a的逆元
简单的推导一下吧:
先两边%b: a*x%b = 1 % b
a*x = 1 (mod b)
所以 x 是 a关于b的逆元
void ex_gcd(LL a,LL b,LL &x,LL &y,LL &d) { if(b==0) { x=1; y=0; } ex_gcd(b,a%b , y, x, d); y -= (a/b)*x; } LL inv(int t , int p) { LL d, x ,y; ex_gcd(t, p, x, y, d); return d == 1 ? (x % p + p) % p : -1; }
方法三:
利用递归的方式求逆元
当p是一个质数的时候有
inv(a) = (p - p / a) * inv(p % a) % p
一直递归到1为止,因为1的逆元就是1
LL inv(LL t, LL p) {//求t关于p的逆元,注意:t要小于p,最好传参前先把t%p一下 return t == 1 ? 1 : (p - p / t) * inv(p % t, p) % p; }