今天正式学了一遍质数筛(已经可以打线性的质数筛了!)以及质因数分解,要做一个小小的总结。
知识点前缀:
- 质数:除了1和它本身没有其他的因数,一般指的都是正整数。;
- 筛法:筛法是一种简单检定素数的算法。
- 筛法的来源:因为希腊人是把数写在涂腊的板上,每要划去一个数,就在上面记以小点,寻求质数的工作完毕后,这许多小点就像一个筛子,所以就把埃拉托斯特尼的方法叫做“埃拉托斯特尼筛法”,简称“筛法”。
- 筛法的具体做法:给出要筛数值的范围n,找出n以内的素数p1,p2,p3,…,pk。先用2去筛,即把2留下,把2的倍数剔除掉;再用下一个素数,也就是3筛,把3留下,把3的倍数剔除掉;接下去用下一个素数5筛,把5留下,把5的倍数剔除掉;不断重复下去…。
- 唯一分解定理:任何一个大于1的正整数都能够被唯一分解为有限个质数的乘积。
质数筛
对于质数筛而言,我们有三种做法:
1.试除法
根据定义出发:若是我们除了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.Eratosthenes 筛法
上面的方法是我们直接判断该数字是不是质数,这样的话,我们每次只可以判断一个数字,效率极低。
那,我们想一下,是否可以直接找出一个范围里的质数呢??
如果找质数不好找的话,我们可以通过找合数啊,正难则反,若是合数都被找出来了,那么最后剩下来的不就都是质数了吗?
那么,我们就可以通过已知的质数(2,3,5……)去标记合数。
从2开始,由小到大扫描每一个数字x,把他的倍数2x,3x,4x……都标记为合数。若扫描到一个数字未被标记,则他便不能被(2,x-1)的数字整除,所以该数字就是质数。
我们可以再继续想一下:对于6而言,他会被2,3标记两次(效率极低)……,实际上,小于 的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;
}
}
3.线性筛选
虽然在上面的算法中有小小的优化,但是同样还是不可以改变数字会被该算法同时好多次标记的本质,比如12,36,60,120……这些数字同样会被2,3……同时进行标记……
所以,我们考虑被进一步的优化。
考虑数字x的因数的唯一确定方法:我们可以采用x的最小的质因数的乘积。比如: ……这样子的话,我们就唯一确定了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的所有的因子,并且把该因子在x中全部除尽,使得x中没有了该因子的参与。
比如:
,那么,对于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]++;
}
推荐题目:(注意问题的转化,这些问题有一些拔高,谨慎选做。)
质数距离
阶乘分解
调整公约数
完……
转载:https://blog.csdn.net/qq_43093454/article/details/101637236