参考的一篇很棒的文章 参考的另一篇很棒的文章
对于素数判定,首先知道最朴素的方法——试除法。从2到sqrt(N),复杂度O (sqrt(N))当然,如果把素数预处理出来,复杂度要优秀一些。由素数分布定理:对正实数x,定义π(x)为素数计数函数,即不大于x的素数个数,则有:
处理出素数以后,只试除2到sqrt(N)之间的素数,复杂度约为。然而对于较大的数还是无能为力,比如2333333333333333333。那么有一个更优秀的算法:Miller_Rabin算法。
首先要知道费马小定理:
已知n为素数,且a满足
那么,证明可以看一看神仙的博客,这里就不赘述了。
然而反过来不一定。比如著名的伪素数341,它满足,然而是一个合数,341=11*31。
1903年,马洛(Malo)证明:若n为伪素数,则
也是一个伪素数,从而肯定了伪素数的个数是无穷的。
等等,如果把底数换成除2以外和n互质的数呢?
呃呃,很可惜,还存在一种Carmichael数,对于所有正整数b,,都有同余式成立。比如561。
好吧,先看一个东西:二次探测——
对于素数p,满足 的同余类有且只有两个, 和
通俗地讲,就是如果,那么x除以p,余数只可能是1或p-1。
如果x%p!=1或p-1且,那么x就一定是合数了。
证明也在神仙的博客,这里不赘述了。
看一下算法的大致流程:
如果是小一点的数,直接先用欧拉筛法预处理。
inline void linear_sieves(int n=maxn-10){
mark[1]=1;
for(int i=2;i<=n;++i){
if(!mark[i]) P[++cnt]=i;
for(int j=1;((j<=cnt)&&(i*P[j]<=n));++j){
mark[i*P[j]]=1;
if(!i%P[j]) break;
}
}
}
对于没有筛到的大数,令其为n:
1.首先判断n是否为几个小素数的倍数。令这几个素数为P[1]~P[10](就测前10个吧),因为要保证n与这个素数互质(费马小定理)。如果n是它们的倍数,直接返回false。
2.否则分解n-1.令,其中m为奇数。通俗地讲,就是把n-1中的2都提出来,求出q和m。
3.分别以P[1]~P[10]作为底数,利用费马小定理和二次探测来检验——首先,假设当前的底数为a。
4.如果有,那么对于一个质数,一定满足(二次探测)。如果不满足的话,一定是合数,直接return false。于是我们可以先算出,然后不断地平方,检验。
5.计算到最后,如果,即,才能说明是极大概率是质数。因为费马小定理是成为质数的必要非充分条件,就是说不满足费马小定理的一定是合数。通俗地讲,经历千辛万苦算到这一步,要是,还是只能安安静静地做一个合数。
5.如果这几个底数全部满足二次探测和费马小定理,那么恭喜这个数,极大极大的概率是一个质数。
复杂度的话,用龟速乘的上限是,其中k为测试的轮数,就是说用了几个素数为底数测试。
然而用神奇的快速乘就变成了。
该部分代码如下:
inline ll mul(ll a,ll b,ll mod){return (a*b-(ll)((long double)a/mod*b)*mod+mod)%mod;}
inline ll quick_pow(ll a,ll b,ll mod,ll ret=1){
for(;b;b>>=1,a=mul(a,a,mod))
if(b&1) ret=mul(ret,a,mod);
return ret;
}
//P数组是用欧拉筛筛出的素数
//这里p代表作为底数的素数,n=(2^q)*m
inline bool check(int p,int q,ll m,ll n){
ll num=quick_pow(p,m,n),pre=num;
while(q--){
num=mul(num,num,n);
if(num==1&&pre!=1&&pre!=n-1) return false;
pre=num;
}return num==1;
}
inline bool isprime(ll x){
if(x<=maxn-10) return !mark[x];
for(int i=1;i<=10;++i) if(!x%P[i]) return false;
int q=0;ll m=x-1;while(!(m&1)) m>>=1,q++;
for(int i=1;i<=10;++i) if(!check(P[i],q,m,x)) return false;
return true;
}
例题:HDU2138
代码:
#include
#define ll long long
using namespace std;
const int maxn=1e5+10;
bitset mark;
int P[maxn],cnt,T,n,ans;
inline ll mul(ll a,ll b,ll mod){return (a*b-(ll)((long double)a/mod*b)*mod+mod)%mod;}
inline ll quick_pow(ll a,ll b,ll mod,ll ret=1){for(;b;b>>=1,a=mul(a,a,mod)) if(b&1) ret=mul(ret,a,mod);return ret;}
inline void linear_sieves(int n=maxn-10){
mark[1]=1;
for(int i=2;i<=n;++i){
if(!mark[i]) P[++cnt]=i;
for(int j=1;((j<=cnt)&&(i*P[j]<=n));++j){
mark[i*P[j]]=1;
if(!i%P[j]) break;
}
}
}
inline bool check(int p,int q,ll m,ll n){
ll num=quick_pow(p,m,n),pre=num;
while(q--){
num=mul(num,num,n);
if(num==1&&pre!=1&&pre!=n-1) return false;
pre=num;
}return num==1;
}
inline bool isprime(ll x){
if(x<=maxn-10) return !mark[x];
for(int i=1;i<=10;++i) if(!x%P[i]) return false;
//(!(m&1))表示m为2的倍数。因为这表示m的二进制的最后一位为0。
int q=0;ll m=x-1;while(!(m&1)) m>>=1,q++;
for(int i=1;i<=10;++i) if(!check(P[i],q,m,x)) return false;
return true;
}
inline int read(){
int x=0;char ch=getchar();
while(!isdigit(ch)) ch=getchar();
while(isdigit(ch)) x=(x<<3)+(x<<1)+ch-'0',ch=getchar();
return x;
}
inline void print(int x){
if(x>9) print(x/10);
putchar(x%10+'0');
}
int main(){
linear_sieves();
while(~scanf("%d",&n)){
ans=0;
while(n--) ans+=isprime(read());
print(ans),putchar(10);
}
}