乘法逆元
我们都知道
(a+b)%p=(a%p+b%p)%p
(a-b)%p=(a%p-b%p)%p
(ab)%p=(a%pb%p)%p
(a/b)%p…不满足
可以这样展开有什么好处呢?
那就是当a和b很大时,可以先对其进行求模运算再运算。但是除法不能用上述公式,那么我们可以考虑一种新的方法解决这个问题。
定义
若在 mod p意义下,对于一个整数a,有a*x ≡ 1(mod p),(这里面面等号不代表恒等于,而是说同余,即(a*x)%p的值与 1(mod p)相等),那么这个整数x即为a的乘法逆元,同时a也为x的乘法逆元。
充要条件
a存在模p的乘法逆元的充要条件是 gcd(a,p) = 1,即a与p互质。
那么求取 (a/b)%p就等同与求 a*(b的逆元)%p。
证明:
假设b的逆元为x,那么 (b*x)(mod p) = 1(mod p) ,那么x=b在数论上的倒数,即x=1/b,这样我们可以吧 (a\b)%p ==> (a*x)%p
证明如下:
(a / b) % p= m …0 ----------------①
①*b(并满足modp情况)得 a%p=m*(b%p)
也就是说 a ≡ m*b (%p)---------- ②
②*x得 a*x≡ m*b*x (%p)
将 b*x 看做一个整体 因为 b*x ≡1 (mod p)
可以将 b*x换为1 ,那么 a*x ≡ m (%p)
因为 (a/b)%p =m 那么 a*x ≡ ((a/b)%p) %p
即 (a*x)%p = (a/b)%p
那么新的问题来了,如何求逆元?
有很多种方法:费马小定理(p为质数)、扩展欧几里得、线性递推…
在信息学竞赛中 p一般都为 质数
那么我们用费马小定理来求一下:
费马小定理
假如 a 是一个整数,p是一个质数,那么
1、如果a是p的倍数,a^p ≡ a (mod p)
2、如果a不是p的倍数,a^(p-1) ≡ 1 (mod p)
注:≡ 表示的不是恒等于,而是上面提到的 模p同余
我们求逆元根据逆元存在的充要条件,a与p肯定是互质的,那么我们就用第二条。
先把第二个式子拿过来:
a^(p-1) ≡ 1 (mod p)
根据上面式子,我们转化成 a a^(p-2) ≡ 1 (mod p)*
那么! a ^ ( p - 2 ) 不就正是 a的逆元了吗。
别高兴太早,真正是a的逆元的是这个 a^(p-2)%p
因为我们的式子本质是 (a%p) * [a^(p-2)%p] = 1%p
Example:
a=5 ,p=3
a^(-1) 代表a的逆元
那么 a^(-1) =2
检验一下 上面这个数是不是 a的逆元:
( 5 * 2 )% 3 =1,1%3=1,所以,上面没有错。
在计算 a^(p-2)我们通常采用快速幂来求解
下面放一个 java快速幂的模板
//mod 为全局变量
public static long quickPow(long base,long power) {
long res=1;
while(power>0) {
if((power&1)==1) {
//如果当前阶数二进制最后一位为1即power为奇数,则令结果将上次偶数次阶算得的base乘到结果中
res=res*base%mod;
}
power>>=1;
base=base*base%mod;
}
return res%mod;
}
Emmm…
刚说完,用java在洛谷交了一道这样的模板题,结果直接整了个这玩意(可能因为我用的是java吧)!!!
可以看出,费马小定理求逆元有点小慢。那么我们看另一种方法
exgec求逆元(O(logn))
这个是利用扩展欧几里得来求线性同余方程 a * x ≡ c (mod p) 的c =1 的情况。
我们就可以转换成 :a*x + p*y =1
直接上代码:
import java.io.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.Queue;
public class Main{
static PrintWriter out=new PrintWriter(new OutputStreamWriter(System.out));
static StreamTokenizer in=new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in)));
static long mod;
static long x,y;
public static void main(String[] args) throws IOException{
long a=100;
long mod=100000007;
exgcd(a,mod);
x=(x%mod+mod)%mod;
out.println(x);//57000004
out.flush();
}
public static void exgcd(long a,long p)
{
if(p==0){
x=1;y=0;
return ;
}
exgcd(p,a%p);
long tmp=x;
x=y;
y=tmp-a/p*y;
}
static int nextInt(){
try {
in.nextToken();
}catch(IOException e){
e.printStackTrace();
}
return (int)in.nval;
}
static double nextDouble(){
try{
in.nextToken();
}catch(IOException e){
e.printStackTrace();
}
return (double)in.nval;
}
static long nextLong(){
try{
in.nextToken();
}catch(IOException e){
e.printStackTrace();
}
return (long)in.nval;
}
static String next(){
try{
in.nextToken();
}catch(IOException e){
e.printStackTrace();
}
return in.sval;
}
}
exgcd和费马小定理只适合用来求单个逆元,求大量数的逆元肯定会超时。
再看另一种方法
线性递推求逆元
看一下递推过程:
设 t = p / i,k = p % i,有:p = i * t + k // i为当前要求逆元的主角
即 i * t + k Ξ 0 (mod p)
即 k Ξ - i * t (mod p)
两边同时除以 i * k
有 1 / i Ξ - t / k (mod p)
将k,t带入
有 inv[ i ] Ξ - p / i * inv[ p % i ] (mod p)
为防止有负数,有inv[ i ] = ( p - p / i * inv[ p % i ] % p ) % p
//此处为小技巧,多加一个p
inv[i]=(p-p/i*inv[p%i]%p)%p;