判断素数
在数论相关问题中,我们经常需要使用素数。在使用之前,首先要判断该数是否为素数。考虑一个数n,根据素数定义,我们可以枚举比其小的质数直到根号n,如果n不是其中任何一个素数的倍数,则n本身为素数。同时我们可以将其记录下来便于更大数的判断。
void isprime(int n){
int x=sqrt(n);int pd=0;
for(int i=1;prime[i]<=x&&i<=cnt;++i){
if(n%prime[i]==0){
pd=1;break;
}
}if(!pd)prime[++cnt]=n;
}
此时发现,任何一个数,要想判断其是否为素数,我们需要先获得比其小的素数。(当然你可以直接枚举比n小的所有数,但那样显然太慢)。
我们将问题扩大:如何将1~n中的所有素数全部找出?如果能做到,那剩下的自然是合数。
比起对每一个数进行判断,更优秀的做法是反向思维,对于每个已经发现的素数,比其大的倍数显然不是素数。由此,我们引入了埃氏筛。
埃氏筛
简单来说,埃氏筛就是从小到大,将每个素数的所有倍数全部打上标记,表示其为合数。当轮到某个数而其仍然未被标记时,该数自然就是一个素数。
该算法复杂度为n lnln n。具体证明超出了我目前的知识范围。
void prime_prepare(){
int pd[N+3]={0};
for(int i=2;i<=N;++i){
if(!pd[i]){
prime[++cnt]=i;
for(int j=1;i*j<=N;++j)
pd[i*j]=1;
}
}
}
考虑如何优化这个算法。可以发现,一个合数被其所有的质数因子筛了一遍。考虑如何优化这个重复过程,我们得到了欧拉筛法。
欧拉筛
欧拉筛的复杂度为n,是极为优秀的线性筛法。那么这是如何做到的呢?
对于每个合数,我们希望它只被自己最小的质数因子筛出。如此一来,每个数至多被筛一次,复杂度即为n。
很显然,因为筛的过程是从小到大进行的,每个数必然首先被其最小的质因子筛出。所以我们的问题只剩下:如何避免一个数被其非最小的质因子筛到。
与埃氏筛不同。埃氏筛的做法,是在发现质数后,用其倍数筛。对于欧拉筛,我们考虑对于任何一个数后,从小到大枚举其质数倍进行筛查。一旦发现当前质数为当前数的约数后,退出循环。
为什么这样是对的呢?考虑一个数s,正在枚举的素数p。
如果p还不是s的约数,则二者相乘,p一定是积的最小质因子(此处可以想想)。
如果p枚举到了s的约数,此时p仍然是积的最小质因子。
但在此之后,因为先前的s含有一个更小的质因子,所以积的最小质因子不再是p。我们要舍弃掉的就是这部分。
void prime_prepare(){
int pd[N+3]={0};
for(int i=2;i<=N;++i){
if(!pd[i])prime[++cnt]=i;
for(int j=1;j<=cnt&&i*prime[j]<=N;++j){
pd[i*prime[j]]=1;
if(i%prime[j]==0)break;
}
}
}