今天正式学了一遍质数筛(已经可以打线性的质数筛了!)以及质因数分解,要做一个小小的总结。
对于质数筛而言,我们有三种做法:
根据定义出发:若是我们除了1和它本身找到了其他的因子,那么这个数字一定不是质数,反之,则为质数。
那么,对于0和1本身,我们既要进行特殊判断,他们既不是质数也不是合数。
枚举因数的时候我们只需要枚举到根号即可,因为这样就可以判断他的所有的因数。(自行思考……)
bool primes(int x)
{
if(x<2) return false;
for(int i=2;i<=sqrt(n);++i)
if(n%i==0) return false;
return true;
}
上面的方法是我们直接判断该数字是不是质数,这样的话,我们每次只可以判断一个数字,效率极低。
那,我们想一下,是否可以直接找出一个范围里的质数呢??
如果找质数不好找的话,我们可以通过找合数啊,正难则反,若是合数都被找出来了,那么最后剩下来的不就都是质数了吗?
那么,我们就可以通过已知的质数(2,3,5……)去标记合数。
从2开始,由小到大扫描每一个数字x,把他的倍数2x,3x,4x……都标记为合数。若扫描到一个数字未被标记,则他便不能被(2,x-1)的数字整除,所以该数字就是质数。
我们可以再继续想一下:对于6而言,他会被2,3标记两次(效率极低)……,实际上,小于 x 2 x^2 x2 的x的倍数在扫描更小的数字的时候就已经被标记过了。所以,我们对于数字x而言,我们从x^2开始标记就可以了。
void primes(int x)
{
memset(v,0,sizeof(v));//合数标记
for(int i=2;i<=n;++i)
{
if(v[i]) continue;
prime.push_back(i);//该数字是质数
for(int j=i;j<=n/i;++j)
v[i*j]=true;
}
}
虽然在上面的算法中有小小的优化,但是同样还是不可以改变数字会被该算法同时好多次标记的本质,比如12,36,60,120……这些数字同样会被2,3……同时进行标记……
所以,我们考虑被进一步的优化。
考虑数字x的因数的唯一确定方法:我们可以采用x的最小的质因数的乘积。比如: 12 = 2 ∗ 2 ∗ 3 , 6 = 2 ∗ 3 12=2*2*3, 6=2*3 12=2∗2∗3,6=2∗3……这样子的话,我们就唯一确定了x的被遍历的方法,所以该数字就只会被遍历一次。
线性筛法的原理:每个合数必有一个最小的质因数的排列方式,用这排列方式把这个合数筛掉。
在每一把该数字的最小的质因子筛完之后,再次遍历到该数字的因数,便可以保证这个因数一定是质因数。(自行思考,还是比较好想的。)
void primes(int x)
{
memset(v,0,sizeof(v));
int m=0;//质数的数量
for(int i=2;i<=x;++i)
{
if(!v[i]) v[i]=i, prime[++m]=i;/*存储质数*/
for(int j=1;j<=m;++j)
{
if(prime[j]>v[i]||i*prime[j]>x) break;
v[i*prime[j]]=v[i];
}
}
}
我们用上面的试除法以及Eratosthenes 筛法,找到 [2, x \sqrt{x} x]中x的所有的因子,并且把该因子在x中全部除尽,使得x中没有了该因子的参与。
比如: 12 = 2 ∗ 2 ∗ 3 12=2*2*3 12=2∗2∗3,那么,对于12而言,我们就需要把两次2都除尽,使得12中只剩下了3的参与。
那么,显然,在上述的过程中可以整除x的数字都是质数。
void primes(int k,int x)
{
for(int i=2;i<=sqrt(x);++i)
{
if(x%i==0)
while(x%i==0)
x=x/i, c[i]++;
}
if(x!=1) c[x]++;
}
推荐题目:(注意问题的转化,有些问题有一些拔高,谨慎选做。)
P3383 【模板】线性筛素数
质数距离
阶乘分解
调整公约数
完……