1.求单个数(n)的关于模数(p)的逆元:
(1)扩展欧几里得算法
满足条件:n与p互质
推导过程:(a*x)%p==(a/b)%p
-->x%p==1/b-1/(b*p)*p
-->x*b%p==1-1/p*p
-->x*b+1/p*p==1
由于b与p互质,所以gcd(b,p)==1,因此用扩展欧几里得求出的x即为b关于p的逆元。
ps.这里求出的逆元可能为负因此要转正。
代码:
1 #include<cstdio> 2 void ex_gcd(int a,int b,int&x,int&y){ 3 if(b==1){ 4 x=0;y=1;return; 5 } 6 ex_gcd(b,a%b,x,y); 7 int t=x; 8 x=y; 9 y=t-a/b*y; 10 } 11 int main(){ 12 int a,b,p,x,y; 13 scanf("%d %d %d",&a,&b,&p); 14 ex_gcd(b,p,x,y); 15 x=(x%p+p)%p; 16 printf("%d",x); 17 return 0; 18 }
(2)快速幂求逆元
满足条件:p为质数
推导过程:
由费马小定理:ap-1%p=1
-->a*ap-2%p=1
-->ap-2即为a关于p的逆元
ps.这个速度没扩欧快......
代码:
1 #include<cstdio> 2 int ksm(long long a,int b,int p){ 3 int res=1; 4 while(b){ 5 if(b&1)res=res*a%p; 6 a=a*a%p; 7 b>>=1; 8 } 9 return res; 10 } 11 int main(){ 12 int a,b,p,x; 13 scanf("%d %d %d",&a,&b,&p); 14 x=ksm(b,p-2,p); 15 printf("%d",x); 16 return 0; 17 }
2.预处理1~n关于p的逆元:
(1)单个求-->就是枚举每个数单独求逆元,复杂度O(nlog(n)),这里不再赘述。
(2)递推求逆元:
推导过程:易知1的逆元为1。
令t=p/i,r=p%i;
-->t*i+r≡0(mod)p
-->t*i≡-r(mod)p
-->两边同时乘上i-1*r-1
-->i-1=(-t+p)*r-1%p
-->inv[i]=(p-p/i)*inv[p%i]%p
代码:
1 #include<cstdio> 2 #include<cstring> 3 #include<algorithm> 4 typedef long long LL; 5 long long inv[3000005]; 6 int main(){ 7 int n,p; 8 scanf("%d %d",&n,&p); 9 inv[1]=1;printf("1 "); 10 for(int i=2;i<=n;i++){ 11 inv[i]=(long long)(p-p/i)*inv[p%i]%p; 12 printf("%lld ",inv[i]); 13 } 14 return 0; 15 }
3.预处理1!~n!关于p的逆元:
(1)递推求阶乘时用扩欧或快速幂求解,复杂度O(nlog(n)),也不重复了。
(2)递推求逆元:
推导过程:设n!的逆元为inv[n]
-->inv[n]=(n!)-1
-->inv[n-1]=(n-1)!-1%p=(n!)-1*i%p
复杂度O(log(n)+n)。
代码:
1 #include<cstdio> 2 int inv[100005]; 3 int ksm(long long a,int b,int p){ 4 int res=1; 5 while(b){ 6 if(b&1)res=res*a%p; 7 a=a*a%p; 8 b>>=1; 9 } 10 return res; 11 } 12 int main(){ 13 int n,p,x; 14 scanf("%d %d",&n,&p);//求1!~n!关于p的逆元 15 int kk=1; 16 for(int i=2;i<=n;i++)kk=kk*i%p; 17 inv[n]=ksm(kk,p-2,p); 18 for(int i=n-1;i>=0;i--)inv[i]=inv[i+1]*(i+1)%p; 19 return 0; 20 }