一.Euler定理
若a与n互质,那么:
证明如下:
设在1~N中与n互质的数为:
将这组数乘以a,得到:
由于与
互质,
也与n互质,那么
也一定与
互质,那么
也一定与n互质,这是因为,假设余数不与
互质,即
,设
与
的公约数为
,且
,设
,那么
这样,就会和n有公约数
,所以
的结果一定是某个
,现假设
,
,那么
,但是
与
是互质的,那么
一定是
的若干倍,但由于
,所以只能
,与前提矛盾,所以
模
的结果互不相同。
因而,将模
得到的数,和
是同一组数。所以,
所以:
特别的,当为质数时,
,那么:
这也就是费马定理。
二.快速幂
1.算法描述
快速幂是指求解的问题。基本思路为:
将写成:
那么:
而我们把的结果记录下来,再相乘即可。
而将化成2的幂之和的形式很容易,只需要将k写成其二进制的形式即可。所以我们通过k&1和k>>=1把每一次k的最后一位求出来,若该位为1,则相应的求出
的结果;若该位为0,则更新a的幂以及将k往右移动一位即可。要记住,k在往右移动的同时,a通过平方也不断更新,使得k与a对应,也就是说此时
是第i位,a对应的也是
。
2.代码实现
若的第i位对应的是
,所以k每移动一位,a平方即可。
int power(int a,int b,int p){
int res=1;
long long x=a;
while(b){
if(b&1) res=res*x%p;
b>>=1;
x=x*x%p;
}
return res;
}
在实现该函数时,有以下几点需要注意:
1.本来应该是,但是其会等价于
2.本来更新为,但是x可以到2e9的数量级,那这样你不断平方,是很容易爆掉的,所以在这里我们也模上p,对于整个结果是没有影响的。也正是这样,x会小于p,res会小于p,所以res还是可以用int定义的,所以在数论中,数字会特别大, 为了防止它爆掉,可以考虑在计算一些中间值时模上p。
3.为什么不可以直接用a,而要用long long 的x代替a?我是觉得毕竟*和%是同一数量级,有可能到后面p也很大的时候,x尽管会小于p,但它也很大,x定义为int的话,x*x就会爆掉,所以在数论中,也一般将数据类型定义为long long.
完整代码:
#include<iostream>
using namespace std;
int power(int a,int b,int p){
int res=1;
long long x=a;
while(b){
if(b&1) res=res*x%p;//如果b&1=1,说明这一位是1,需要乘以这个求余的结果
//需要注意的是,开始是最后一位对应的就是a
b>>=1;//往右移一位
x=x*x%p;//更新a的值,平方
}
return res;
}
int main(){
int n;
cin>>n;
while(n--){
int a,b,p;
scanf("%d%d%d",&a,&b,&p);
cout<<power(a,b,p)<<endl;
}
}
3.时间复杂度
这样,去求k的二进制,时间复杂度为O(logk)。
三.快速幂求逆元
1.算法描述:
逆元的定义:若正整数m,b互质,且对于任意b|a的正整数a,若存在整数x,使得
则称x为b的模m的逆元,记作:
将上式转化一下可得:
由于对任意的a成立,那么:
此时我们考虑两种情况:
1.b是m的倍数,那么,此时b不存在逆元。
2.b不是m的倍数,注意到b与m互质,且m为质数的情况下,由费马定理:
这样:
因而,对于给定的b以及m,在b和m互质且m为质数的前提下,当b是m的倍数时,不存在b的模m的逆元;若b不是m的倍数,存在b的逆元为。
所以每次对于给定的满足b|a的a,要去求它模上m的余数,可以计算a*x模上m的余数,将除法转化为乘法。
2.代码实现
这样,代码就很好实现了,对第二部分快速幂的模板稍作修改即可,即k=m-2,另外在输出时,加上判断b是否时m的语句即可。
具体代码为:
#include<iostream>
using namespace std;
int power(int a,int p){
int k=p-2;
long long x=a;
int res=1;
while(k){
if(k&1) res=res*x%p;
k>>=1;
x=x*x%p;
}
return res;
}
int main(){
int n;
cin>>n;
while(n--){
int a,p;
scanf("%d%d",&a,&p);
if(a%p==0){
cout<<"impossible"<<endl;
}
else{
cout<<power(a,p)<<endl;
}
}
}