模意义下的除法在大多数时候都不适用。
当题目中要求对答案取模,但我们又不得不使用一般的除法的时候,就需要用逆元的乘法来代替除法。
在模意义下,a mod m的逆元(下文中用inv[a]代替)就是一个数inv[x]使得:
那么除法就可以转换成:
注意逆元是定义在x与m互质的情况下的,也就是当x与m不互质时,x模m的逆元不存在。
费马小定理是我最常用的,只要互素就可以用了,一般而言模数是素数时只要a不是模数的倍数就可以使用了。
时间复杂度 O ( l o g n ) O(logn) O(logn),适用于大多数时候
在m为素数时
a m − 1 ≡ 1 ( m o d m ) a^{m-1}≡ 1 (mod m) am−1≡1 (mod m)
左右同时乘上 i n v [ a ] inv[a] inv[a]:
i n v [ a ] ∗ a m − 1 ≡ i n v [ a ] ( m o d m ) inv[a] * a^{m-1} ≡ inv[a] (mod m) inv[a]∗am−1≡inv[a] (mod m)
把 i n v [ a ] inv[a] inv[a]换成 a − 1 a^{-1} a−1可得:
a − 1 ∗ a m − 1 ≡ a m − 2 ≡ i n v [ a ] ( m o d m ) a^{-1} * a^{m-1} ≡ a^{m-2} ≡ inv[a] (mod m) a−1∗am−1≡am−2≡inv[a] (mod m)
好的那么 a mod m 的逆元就是:
i n v [ a ] = a m − 2 % m inv[a] = a^{m-2}\%m inv[a]=am−2%m
那么欧拉定理也是一样的 a ϕ ( m ) ≡ 1 ( m o d m ) a^{\phi(m)}≡ 1 (mod m) aϕ(m)≡1 (mod m)
同理可得 i n v [ a ] = a ϕ ( m ) − 1 % m inv[a] = a^{\phi(m)-1}\%m inv[a]=aϕ(m)−1%m
欧拉定理适用于m不是素数的情况
在做模数可变的时候比较可靠(但是一般要利用欧拉函数是积性函数打欧拉筛搞那个 p h i phi phi)
typedef long long LL;
LL Pow(LL x,LL p,int mod) {
if(p==0) return 1;
if(p%2==0) return Pow(x*x%mod,p/2,mod);
return x*Pow(x*x%mod,p/2,mod)%mod;
}
inv[a]=Pow(a,m-2,m); //MOD is prime
inv[a]=Pow(a,phi[m]-1,m); //init -> phi
关于欧拉函数的计算戳这里
复杂度 O ( l o g n ) O(logn) O(logn)
适用性强于费马小定理
但是其实感觉还是 Pow 比 Exgcd 好打一点哈
当 a 与 b 互素时有 ( a , b ) = 1 ;
即得: a ∗ x + b ∗ y = 1 a * x + b * y = 1 a∗x+b∗y=1
a ∗ x ≡ 1 ( m o d b ) a * x ≡ 1 ( mod b ) a∗x≡1 (mod b)
i n v [ a ] ∗ a ∗ x ≡ i n v [ a ] ( m o d b ) inv[a] * a * x ≡ inv[a] ( mod b ) inv[a]∗a∗x≡inv[a] (mod b)
i n v [ a ] ≡ x ( m o d b ) inv[a] ≡ x ( mod b ) inv[a]≡x (mod b)
因此 x 是 a mod b 的逆元;
蒟蒻表示自己并不怎么会Exgcd
挖下巨坑以后再补哈哈
复杂度 O ( n ) O(n) O(n)
当需要很多数的逆元时,例如我们需要前n个数的逆元时
如果用前两个算法挨个计算,对于前n个数会是 O ( n l o g n ) O(nlogn) O(nlogn)的复杂度
介绍一种线性逆元算法:
原理:假设我们需要求 x mod m 的逆元
首先我们需要一句废话: m ≡ 0 ( m o d m ) m ≡ 0 ( mod m ) m≡0 (mod m)
我们知道: m = ⌊ m x ⌋ ∗ x + m % x m =\lfloor \frac{m}{x} \rfloor *x+m \% x m=⌊xm⌋∗x+m%x
所以考虑让x“落单”,我们可以先移项 ⌊ m x ⌋ ∗ x ≡ − m % x ( m o d m ) \lfloor \frac{m}{x} \rfloor *x ≡ -m \% x ( mod m ) ⌊xm⌋∗x≡−m%x (mod m)
接着可以使用带入逆元了 ⌊ m x ⌋ ∗ x ∗ i n v [ x ] ≡ ( − m % x ) ∗ i n v [ x ] ( m o d m ) \lfloor \frac{m}{x} \rfloor *x*inv[x] ≡ (-m \% x)*inv[x] ( mod m ) ⌊xm⌋∗x∗inv[x]≡(−m%x)∗inv[x] (mod m)
⌊ m x ⌋ ≡ ( − m % x ) ∗ i n v [ x ] ( m o d m ) \lfloor \frac{m}{x} \rfloor ≡ (-m \% x)*inv[x] ( mod m ) ⌊xm⌋≡(−m%x)∗inv[x] (mod m)
然而好像不好看,考虑利用m%x的逆元转换到x的逆元(左边是为了避免负数加了个m)
m − ⌊ m x ⌋ ≡ ( m % x ) ∗ i n v [ x ] ( m o d m ) m-\lfloor \frac{m}{x} \rfloor ≡ (m \% x)*inv[x] ( mod m ) m−⌊xm⌋≡(m%x)∗inv[x] (mod m)
( m − ⌊ m x ⌋ ) ∗ i n v [ m % x ] ≡ ( m % x ) ∗ i n v [ x ] ∗ i n v [ m % x ] ( m o d m ) (m-\lfloor \frac{m}{x} \rfloor )*inv[m \% x] ≡ (m \% x)*inv[x]*inv[m \% x] ( mod m ) (m−⌊xm⌋)∗inv[m%x]≡(m%x)∗inv[x]∗inv[m%x] (mod m)
i n v [ x ] ≡ ( m − ⌊ m x ⌋ ) ∗ i n v [ m % x ] ( m o d m ) inv[x] ≡ (m-\lfloor \frac{m}{x} \rfloor )*inv[m \% x] ( mod m ) inv[x]≡(m−⌊xm⌋)∗inv[m%x] (mod m)
我们惊奇地发现 x > m % x 恒 成 立 x>m\%x 恒成立 x>m%x 恒成立
这就意味着我们以一种DP的思路从小到大更新inv数组,可以以 O ( n ) O(n) O(n)的复杂度更新出来
i n v [ x ] ≡ ( m − ⌊ m x ⌋ ) ∗ i n v [ m % x ] % m inv[x] ≡ (m-\lfloor \frac{m}{x} \rfloor )*inv[m \% x] \% m inv[x]≡(m−⌊xm⌋)∗inv[m%x]%m
typedef long long LL;
inv[1]=1;
for(int i=2;i<=n;i++)
inv[i]=((LL)(MOD-MOD/i)*inv[MOD%i])%MOD;
复杂度 O ( n ) O(n) O(n)
在求组合数的那些问题时,往往数比较大,涉及除法,有大量阶乘运算
在这里定义 x ! x! x! mod m 的逆元为 i n v [ x ] inv[x] inv[x](不要和上面搞混了)
好的首先我们知道:
( x ! ) − 1 = [ ( x + 1 ) ! ] − 1 ∗ ( x + 1 ) (x!)^{-1}=[(x+1)!]^{-1}*(x+1) (x!)−1=[(x+1)!]−1∗(x+1)
那么这个逆元其实很简单啦
i n v [ x ] = i n v [ x + 1 ] ∗ ( x + 1 ) inv[x]=inv[x+1]*(x+1) inv[x]=inv[x+1]∗(x+1)
不过这个好像是由x+1推到x
所以我们可以用先前的办法 O ( l o g n ) O(logn) O(logn)求出 i n v [ x ] inv[x] inv[x](当然你需要先求到x的阶乘)
然后就可以倒退着把所有的逆元求出来了
LL Pow(LL x,LL p,int mod) {
if(p==0) return 1;
if(p%2==0) return Pow(x*x%mod,p/2,mod);
return x*Pow(x*x%mod,p/2,mod)%mod;
}
x=1;
for(int i=2;i<=n;i++)
x=((LL)x*i)%m;
inv[n]=Pow(x,m-2,m);
for(int i=n-1;i>=1;i--)
inv[i]=((LL)inv[i+1]*(i+1))%m;