稚嫩判定
素数判定很早我们就了解了,是时候回味下当时青涩的代码了。
bool isprime(int x){
int MAX=sqrt(x)+0.1;
if(x<2)return false;
for(int i=2;i<=MAX;i++){
if(x%i==0)return false;
}
return true;
}
普通筛
然而当我们大量的需要素数判定的时候这样就需要打表了,这样就需要素数筛了!
普通筛:
int prime[10000000],primesize;
bool isprime[10000000];
void get_prime(int size){
memset(isprime,true,sizeof isprime);
isprime[0]=isprime[1]=false;
for(int i=2;i<=size;i++){
if(!isprime[i])continue;
prime[primesize++]=i;
for(int j=2*i;j<=size;j+=i)isprime[j]=false;
}
}
欧拉筛
但是我们发现这样其实浪费了好多时间,比如6他竟然被筛掉了两次,哎,浪费啊。
那我们想,如果每个数只能被最小质因子筛掉就好了。我们知道任何数都是素数的乘积,那我们把素数乘积筛掉不就好了吗?那怎么保证最小素数筛掉呢?if(i%prime[j]==0)break;就可以了。
int prime[10000000],primesize;
bool isprime[10000000];
void get_prime(int size){
memset(isprime,true,sizeof isprime);
isprime[0]=isprime[1]=false;
for(int i=2;i<=size;i++){
if(isprime[i])prime[primesize++]=i;
for(int j=0;j<primesize&&i*prime[j]<=size;j++){
isprime[i*prime[j]]=false;
if(i%prime[j]==0)break;
}
}
}
顺便提一下,欧拉函数phi的一些性质,phi[x]表示小于x并且与x互质的数的个数是多少。易知(下面p代表素数):
若
也可以容斥定理稍加分析就出来了。
https://blog.csdn.net/qq_41635132/article/details/82460942。
https://blog.csdn.net/qq_41635132/article/details/82460062。
小于 并且与 互质的数的和为 ,上次ccpc网络赛这道题目,被橘子大佬给秀了。
证明:如果 反证法易证。
所以,与 互质的数都是成对出现,并且和为 ,所以 。
当 时有
那么phi的筛法可以根据素数筛得来。
根据下面三条性质。
int prime[10000000],primesize,phi[10000000];
bool isprime[10000000];
void get_prime(int size){
memset(isprime,true,sizeof isprime);
isprime[0]=isprime[1]=false;
phi[0]=0,phi[1]=1;
for(int i=2;i<=size;i++){
if(isprime[i]){
prime[primesize++]=i;
phi[i]=i-1;
}
for(int j=0;j<primesize&&i*prime[j]<=size;j++){
isprime[i*prime[j]]=false;
if(i%prime[j]==0){
phi[i*prime[j]]=phi[i]*prime[j];
break;
}
phi[i*prime[j]]=phi[i]*(prime[j]-1);
}
}
}
根据第五个性质,可以 的得到phi(n),适用于无法打表的情况。
ll Getphi(ll n){
ll phi=n;
for(ll i=2;i*i<=n;i++){
if(n%i==0){
phi/=i;
phi*=i-1;
while(n%i==0)n/=i;
}
}
if(n!=1)phi/=n,phi*=n-1;
return phi;
}
最后,来展示神级公式
Miller-Rabin大素数测试
参照https://blog.csdn.net/ECNU_LZJ/article/details/72675595这个博客。
仔细看肯定能看懂,我代码里面会有我自己的解释,代码还是挺短,边理解边背也可以。
ll qmul(ll a,ll b,ll mod){//快速积代码
ll res=0;
while(b){
if(b&1)res=(res+a)%mod;
a=(a+a)%mod;
b>>=1;
}
return res;
}
ll qpow(ll a,ll n,ll mod){//快速幂代码
ll res=1;
while(n){
if(n&1)res=qmul(res,a,mod);
a=qmul(a,a,mod);
n>>=1;
}
return res;
}
bool Miller_Rabin(ll n){
//初等判断
if(n==2)return true;
if(n<2||!(n&1))return false;
//计算n-1=2^q*m
ll m=n-1,k=0;
while(!(m&1))k++,m>>=1;
//Miller_Rabin测试次数
for(int i=1;i<=20;i++){
ll a=rand()%(n-1)+1;
ll x=qpow(a,m,n);
ll y;
//对a^m,a^2m,a^4m……进行测试
for(int j=1;j<=k;j++){
y=qmul(x,x,n);
if(y==1&&x!=1&&x!=n-1)return false;
x=y;
}
//二次探测定理,不是1肯定为合数
if(y!=1)return false;
}
return true;
}
但是这是对Miller-Rabin筛初步的理解,我们应该究其更深!
如果我们在上个世纪没有这个算法的时候会怎么想?
当然是粗略的筛一下!
比如我们知道除了2之外所有的偶数都是合数,这就是筛2之后的结果。
我们可以依次向后筛,等我们筛到13的时候,就剩下
那我们的工作就是寻找素性更强的筛法,于是乎欧拉托梦告诉我一个公式
https://blog.csdn.net/qq_41635132/article/details/82459692
那么我们现在有一个 ,素性未知,我们当然可以先搞一搞这个 了。我们把 转化成
然后我们任选一个 ,如果 , 才有可能为素数。同理
是不是很熟悉?这就是费马小定理,有兴趣可以转的我的那一篇博客, https://blog.csdn.net/qq_41635132/article/details/82460062。现在思路清晰了吧?看看上面代码是不是就十分小儿科了。
杜教筛
水平不够,过段时间写。