前言
对于模运算来说:
( a + b ) m o d p = ( a m o d p + b m o d p ) m o d p (a+b)\mod p=(a\mod p+b\mod p)\mod p (a+b)modp=(amodp+bmodp)modp
( a − b ) m o d p = ( a m o d p + b m o d p ) m o d p (a-b)\mod p=(a\mod p +b\mod p)\mod p (a−b)modp=(amodp+bmodp)modp
( a × b ) m o d p = ( a m o d p × b m o d p ) m o d p (a\times b)\mod p=(a\mod p \times b\mod p)\mod p (a×b)modp=(amodp×bmodp)modp
a b m o d p = ( a m o d p ) b m o d p a^b\mod p=(a\mod p)^b\mod p abmodp=(amodp)bmodp
我们发现模运算不满足除法。
那么如果要对分数取模的话,我们就要使用逆元。
逆元就是在模意义下的倒数,但并不是就是倒数!逆元可以表示为:
a b m o d p = a × i n v ( b ) m o d p \frac {a}{b}\mod p =a\times inv(b)\mod p bamodp=a×inv(b)modp
其中
b × i n v ( b ) ≡ 1 ( m o d p ) b\times inv(b)\equiv1(\mod p) b×inv(b)≡1(modp)
快速幂求解逆元
当p为质数时:
根据费马小定理,
a p − 1 ≡ 1 ( m o d p ) a^{p-1}\equiv1(\mod p) ap−1≡1(modp)
a × a p − 2 ≡ 1 ( m o d p ) a\times a^{p-2}\equiv 1 (\mod p) a×ap−2≡1(modp)
a p − 2 a^{p-2} ap−2即为逆元。使用快速幂可以快速求出。废话
时间复杂度O(logn)
代码实现:
#include<bits/stdc++.h>
using namespace std;
int mod,x;
inline int fp(int a,int b)
{
a%=mod;
int ans=1;
while(b)
{
if(b&1) ans=(ans*a)%mod;
a=(a*a)%mod;
b>>=1;
}
return ans;
}
int main()
{
scanf("%d%d",&x,&mod);
printf("%d\n",fp(x,mod-2)%mod);
return 0;
}
扩展欧几里得求逆元
根据定义
b × i n v ( b ) ≡ 1 ( m o d p ) b\times inv(b)\equiv1(\mod p) b×inv(b)≡1(modp)
等价于
b × i n v ( b ) + p × y = 1 b\times inv(b)+p\times y =1 b×inv(b)+p×y=1
即对于
b × x + p × y = 1 b\times x+p\times y =1 b×x+p×y=1
求解 x x x
根据上次讲的拓展欧几里得求即可。上一期传送门
时间复杂度O(logn),对于 p p p不是质数,也可以解。
代码实现:
#include<bits/stdc++.h>
using namespace std;
int b,mod,x,y;
inline 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;
}
int main()
{
scanf("%d%d",&b,&mod);
exgcd(b,mod,x,y);
while(x<0) x+=mod;
printf("%d",x);
}
线性递推求逆元
当 p p p是质数时,
递推过程:
设 t = p / i , k = p m o d i t=p/i,k=p\mod i t=p/i,k=pmodi
易得:
t × i + k ≡ 0 ( m o d p ) t\times i+k\equiv0(\mod p) t×i+k≡0(modp)
即 k ≡ − t × i ( m o d p ) k\equiv -t\times i(\mod p) k≡−t×i(modp)
两边同除以 i × k i\times k i×k
i n v ( i ) ≡ − t × i n v ( k ) ( m o d p ) inv(i)\equiv-t\times inv(k)(\mod p) inv(i)≡−t×inv(k)(modp)
带入 t , k t,k t,k
i n v ( i ) ≡ − p / i × i n v ( p m o d i ) ( m o d p ) inv(i)\equiv-p/i\times inv(p\mod i)(\mod p) inv(i)≡−p/i×inv(pmodi)(modp)
为了避免负数,可以加 p p p且不影响结果,将 m o d p \mod p modp提出最终得到:
i n v ( i ) ≡ ( p − p / i ) × i n v ( p m o d i ) m o d p inv(i)\equiv(p-p/i)\times inv(p\mod i)\mod p inv(i)≡(p−p/i)×inv(pmodi)modp
这样线性递推了。而且一次就求出了 1 n 1~n 1 n所有的逆元,所以适合大量的求逆元,而上面两种算法都是对单个求逆元,如果对n个求逆元,将会到达O(nlogn),而使用线性递推复杂度为O(n)。
代码实现:
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define num ch-'0'
void get(int &res)
{
char ch;bool flag=0;
while(!isdigit(ch=getchar()))
(ch=='-')&&(flag=true);
for(res=num;isdigit(ch=getchar());res=res*10+num);
(flag)&&(res=-res);
}
ll inv[200000005];
ll n,p;
int main()
{
scanf("%lld%lld",&n,&p);
inv[1]=1;
cout<<1<<endl;
for(ll i=2;i<=n;i++)
{
inv[i]=(p-p/i)*inv[p%i]%p;
cout<<inv[i]<<endl; //printf才能过
}
return 0;
}