乘法逆元及两道模板题详解
乘法逆元
就是
此时b就是a模p意义下的逆元,即
下面我们用inv[a]表示a模p意义下的逆元。
逆元是好东西啊
有时候我们需要算出 a/b mod p 的值,用朴素的方法,我们只能在 a 上不断加 p ,直到它能被 b 整除为止。
当 a,b,p 都很大的时候,这种方法就只能凉凉了,但如果有了逆元,我们就可以非常方便,快捷地求解。
——————某位大佬的话
所以我先讲讲逆元性质:
唯一性就不用讲了
1.积性
假如a与b互质,
2.乘变除
证明如下:
至于逆元的求法,有很多,我只讲四种。
首先,是求单个逆元
1.费马小定理
当 p 为素数时:
所以
所以 就是a在模p意义下的逆元,快速幂求出 即可。
此方法的局限就是p只能是质数。
1 int ksm(int t) 2 { 3 if(t==1)return n%p; 4 if(t%2==0) 5 return ksm(t>>1)*ksm(t>>1)%p; 6 else 7 return ksm(t>>1)*ksm(t>>1)*(n)%p; 8 } 9 int main() 10 { 11 scanf("%d%d",&n,&p); 12 printf("%d",ksm(p-2)); 13 }
2.扩展欧几里得
变一下,我们把p移到左边就成了
因为y是变量所以可以变成这样:
我们把设成x,就变成了扩欧的标准式:
就可以瞎搞了,至于不知道怎么瞎搞的同学,可以先点这里exgcd入门以及同余基础
1 inline void exgcd(ll a,ll b) 2 { 3 if(!b) 4 {x=1;y=0;return;} 5 exgcd(b,a%b); 6 k=x;x=y; 7 y=k-a/b*y; 8 return; 9 } 10 int main() 11 { 12 scanf("%d%d",&n,&p); 13 exgcd(n,p); 14 printf("%d",x); 15 }
然后,就是求多个逆元
1.欧拉函数筛法
在数论,对正整数n,欧拉函数是小于或等于n的正整数中与n互质的数的数目(φ(1)=1)。 ——————百度百科
我们设表示小于等于x且与x互质的正整数的个数。
通式如下:
还有欧拉函数有如下几点性质:
- 欧拉函数是积性函数——若m,n互质,
- 假如n为奇数,
- 假如n为质数,
欧拉函数与费马小定理有点不明不白的关系
对任何两个互质的正整数a, m(m>=2)有
这就是欧拉定理
所以类比于费马小定理的证法:
所以
所以我们要求的就是,又因为欧拉函数是积性函数,所以把m筛一下质数就好了。
不过有更好的办法,这里就不写代码了(其实不会)
2.线性递推
证明好麻烦啊,那就不证了
反正就是设;
再往代
最后得出:
讲完了,现在讲题目
我要讲洛谷的两道题:
这就是模板题,直接套线性递推,就能ac了
1 int main() 2 { 3 int n,m; 4 read(n),read(m); 5 inv[1]=1; 6 for(register int i=2;i<=n;i++) 7 inv[i]=(long long)(m-m/i)*inv[m%i]%m;\\加一个m是为了去负 8 for(register int i=1;i<=n;i++) 9 printf("%d\n",inv[i]); 10 }
值得一提的是,这题扩欧卡一卡也可以过(好水)
1 long long k,x,y; 2 inline void read(long long &x) 3 { 4 x=0; 5 int f=1; 6 char ch=getchar(); 7 while(ch<'0'||ch>'9') 8 { 9 if(ch=='-') 10 f=-1; 11 ch=getchar(); 12 } 13 while(ch>='0'&&ch<='9') 14 { 15 x=x*10+ch-'0'; 16 ch=getchar(); 17 }x*=f; 18 } 19 inline void exgcd(int a,long long b) 20 { 21 if(b==0) 22 {x=1;y=0;return;} 23 exgcd(b,a%b); 24 k=x;x=y; 25 y=k-a/b*y; 26 return; 27 } 28 int main() 29 { 30 long long n,m; 31 read(n),read(m); 32 for(register int i=1;i<=n;i++) 33 { 34 exgcd(i,m); 35 if(x<=0)x+=m; 36 printf("%lld\n",x); 37 } 38 }
对了,还有一个,费马小定理算法。
得分48,a了3个,t了3个
1 inline long long ksm(int t,int i)//记得开long long不然第三个点会wa 2 { 3 if(t==1)return i%m; 4 if(t%2==0) 5 return ksm(t>>1,i)*ksm(t>>1,i)%m; 6 else 7 return ksm(t>>1,i)*ksm(t>>1,i)*(i)%m; 8 } 9 int main() 10 { 11 long long n; 12 io::begin(); 13 io::read(n); 14 io::read(m); 15 for(register int i=1;i<=n;i++) 16 printf("%lld\n",ksm(m-2,i)); 17 }
但是我发现,去掉 inline,就a了4个点,得分64.
所以给大家一个忠告,在递归函数下,inline不是一个好的卡常方法!
因为使用内联函数后虽然调用函数的开销降低了,但是有利必有弊,内联函数会导致主函数指令增多、函数体积增大等情况。
这题就这样
一看数据范围还是很吓人的,,怕不是要打高精,后来发现不是这样,不过题解中真有高精算法,大佬!!
首先,先看我最开始引用的话,嗯,有点道理
然后解析题目发现:本题就是求b在mod p的意义下的逆元。
所以扩欧一遍过,只是读入时注意一下边读边模就好了;
1 inline void exgcd(int a,int b) 2 { 3 if(!b) 4 {x=1;y=0;pos=a;return;} 5 exgcd(b,a%b); 6 k=x;x=y; 7 y=k-a/b*y; 8 return; 9 } 10 int main() 11 { 12 std::cin>>a1>>b1; 13 int n=0,m=0; 14 for(register int i=0;i<strlen(a1);i++) 15 n=(n*10+a1[i]-48)%mod; 16 for(register int i=0;i<strlen(b1);i++) 17 m=(m*10+b1[i]-48)%mod; 18 exgcd(m,mod); 19 if(pos!=1)printf("Angry!"); 20 else 21 { 22 if(x<0)x+=mod; 23 printf("%lld",(1ll*n*x)%mod); 24 } 25 }
最后通过这道题的大质数给大家念一首诗:苟利国家生死以