素数&&算法
素数是个神奇的东西,在数论里经常用到,有很多奇奇怪怪的性质,但是判断一个数是不是素数就是个很麻烦的事情。
1.定义
素数就是质数,两个名词指的是同一种数。 素数的定义是:素数为在大于1的自然数中,除了1和它本身以外不再有其他因数。和素数相反的就是合数,当然合数也是大于1的,
其中注意两点:
- 素数一定大于1,也就是说1不是素数
- 偶数只有2是质数,其余都是素数
2.素数判断
① 朴素算法
判断素数最基础的办法就是测试这个数有没有除了1和自身的其他因数,算法实现也很简单,如下:
bool isprime(int n){
if(n<2) return false;
for(int i=2;i*i<=n;i++){
if(n%i==0) return false;
}
return true;
}
复杂度O(N1/2)
②埃拉托斯特尼筛法
对于判断一定范围内数是否是素数的问题,朴素的算法就要把区间内的每个数都判断一遍,时间复杂度就会变得很高,这种情况下我们可以对区间内的数进行快速预处理,之后判断是否是素数只需要查询区间内的表就行了。
埃式筛法的原理是:一个合数a必定是由一个小于等于a1/2的素数和另一个数相乘得到的。基于此原理,对于区间[2,n]之间的数,我们首先假设区间内所有数都是素数,然后从第一个数开始,如果这个数是质数,就把它的倍数在表中标记为合数,直到找到n1/2为止停止搜索。代码如下:
static const int maxn = 1e6+7;
int isprime[maxn];
void sleve(int n){
for(int i=1;i<=n;i++) isprime[i] = 1; //初始化
isprime[1] = 0; //1不是素数
for(int i=2;i*i<=n;i++){
if(isprime[i]){ //如果是素数,把它的倍数标记成合数
for(int j=i+i;j<=n;j+=i){
isprime[j] = 0;
}
}
}
}
复杂度O(NloglogN)
例题:http://acm.hdu.edu.cn/showproblem.php?pid=2098
http://acm.hdu.edu.cn/showproblem.php?pid=2136
③线性筛法
线性筛法就是在埃式筛法的基础上略微优化了一下,时间复杂度减少了一点,一般情况下用埃式筛法就够了。线性筛法基于这样的原理:一个合数a必定有它的最小的素因数,我们只要把这个最小素因数的倍数标记成合数就好了。其中需要把n1/2以内的素数给记录下来,代码:
static const int maxn = 1e6+7;
int isprime[maxn];
int prime[maxn]; //记录素数
void linear_sleve(int n){
for(int i=1;i<=n;i++) isprime[i] = 1; //初始化
isprime[1] = 0; //1不是素数
cnt = 0; //记录当前素数的个数
for(int i=2;i<=n;i++){
if(isprime[i]){
prime[++cnt] = i;
}
for(int j=1;j<=cnt&&i*prime[j]<=n;j++){
isprime[i*prime[j]] = 0;
if(i%prime[j]==0) break; //这时候i已经有因数prime[j]了,也就是说prime[j]*i的最小素因数已经存在在i中了
}
}
}
复杂度O(N)
重头戏来了!!!
④Miller——Rabin素数测试
对于上面的几个判断素数的办法,可以解决数量级比较小的素数判断,但是对于数量级很大的素数,n1/2也会超时的这种,就要寻找其他的判断素数的解决方案。
比较常用的就是Miller——Rabin素数测试。因为是测试,所以不能确定这个数就是素数,但是这个数是素数的概率非常高,不是素数的概率几乎为0,小到可以接受。
对于这个算法,首先要知道费马小定理
在p是素数的情况下,对于任意整数a有如下等式成立:
这就是费马小定理,当a不能被p整除的情况下,费马小定理可以简化成下面的等式:
这个等式表示的时:a的p-1次方对p取模等于1
基于费马小定理我们会想到用费马测试来判断一个数p是否是素数,即:
对于数p,我们取a∈[1,p-1]中的一部分数来对p进行测试,判断ap-1 ≡ 1 (mod p)是否成立,如果通过了所有的测试,那数p就有大概率是个素数,错误率降低到期望以下。
但是实际上存在一些被叫作Carmichael的数,比如561、1105、1729,对于这些数,我们取a∈[1,p-1]之间的所有数对p进行测试,结果每个数都能通过测试。所以费马测试是存在缺陷的。
那么我们就要另寻解决方案。那就是米勒拉宾素数测试。
Miller—Rabin素数测试基于二次探测定理:
证明如下:
因为p是素数,所以p不能整除x+1也不能整除x-1,所以p只有等于1或者p-1的情况下等式才能成立
现在我们利用 费马小定理 和 二次探测定理 来测试数p是否是素数:
首先,我们将2单独判断
其次,对于偶数,则必然不是素数,直接排除。
对于奇数,我们进行多次测试:
首先我们选取一个数x用于对p进行费马测试
如果xp-1≡1 (mod p) 那么p就有可能是素数
根据二次探测定理
如果x(p-1)/2 ≡1 (mod p)或者x(p-1)/2 ≡ p-1 (mod p)成立,那么它的上一层xp-1≡1 (mod p)必然成立
我们先把p-1用 d·2r 表示(d为奇数,不再有因数2)
我们从d开始不断利用二次探测定理递推,直到d=(p-1)/2为止
如果出现xd·2^r ≡ 1 (mod p)或者xd·2^r ≡ p-1 (mod p),那么就通过了二次探测定理的测试,同时也通过了费马测试。还是直接看代码明白点叭:
long long qpow(long long a,long long b,long long m){ //快速幂取模
long long ans = 1;
while(b){
if(b&1) ans = (ans*a)%m;
b>>=1;
a = (a*a)%m;
}
return ans;
}
bool Miller_Rabin(int x,int p){ //素数测试
int d = p-1;
while(!(d&1)) d>>=1; //计算出d
long long t = qpow(x,d,p);
while(d!=p-1){
if(t==1||t==p-1) return true; //如果算出了t=p-1之后t必定是1
t = (t*t)%p;
d<<=1;
}
return false;
}
bool isprime(int p){ //素数判断主函数
if(p<2) return false;
int a[] = {2,3,5,7,11}; //用于测试的数
for(int i=0;i<5;i++){
if(p==a[i]) return true;
if(!Miller_Rabin(a[i],p)) return false; //用a[i]对p进行测试
}
return true; //通过了所有测试,很大概率是素数
}
复杂度O(logN)