今天hz大神回来给我们上课,首先讲了线性筛选素数,就先做个记录吧
以一道模板题为例子
洛谷题号: P3383 【模板】线性筛素数
题目描述
如题,给定一个范围N,你需要处理M个某数字是否为质数的询问(每个数字均在范围1-N内)
输入输出格式
输入格式:
第一行包含两个正整数N、M,分别表示查询的范围和查询的个数。
接下来M行每行包含一个不小于1且不大于N的整数,即询问概数是否为质数。
输出格式:
输出包含M行,每行为Yes或No,即依次为每一个询问的结果。
输入输出样例
输入样例#1:
100 5
2
3
4
91
97
输出样例#1:
Yes
Yes
No
No
Yes
说明
时空限制:500ms 128M
数据规模:
对于30%的数据:N<=10000,M<=10000
对于100%的数据:N<=10000000,M<=100000
样例说明:
N=100,说明接下来的询问数均不大于100且大于1。
所以2、3、97为质数,4、91非质数。
故依次输出Yes、Yes、No、No、Yes。
.
.
.
分析:这模板题,肯定用线性筛选啦,我先大概说说我对线性筛选素数的理解。
这个方法主要就是用已知的质数去筛出合数。任意一个合数都可以被质因数分解,那么我们就可以用已知的一个素数去筛选出一个以这个质数为最小质因数的合数。
为什么是筛选出一个以这个质数为最小质因数的合数呢? 因为一个合数的质因数可能有很多个,但是最小质因数就只有一个。在这个算法里,一个数如果是质数,就不用筛,如果是合数,就只会被它的最小质因数给筛出来。这样就达到的O(n)的复杂度啦。
.
.
.
直接上代码,代码里有详细注释
//第一种打法
#include
#include
#include
int pd[10800100],pr[10080100];
int n,m,x,tot=0;
using namespace std;
void prime()
{
for(int i=2;i<=n;i++)
{
if(pd[i]==1)
pr[++tot]=i;//pr数组保存素数
for(int j=1;j<=tot;j++)
{
if(pr[j]*i>n) break;//超过范围,退出
pd[pr[j]*i]=0;
if(i%pr[j]==0) break;//已经筛完了以i为最大因数的数
}
}
}
int main()
{
for(int i=1;i<=10000800;i++) pd[i]=1;
//pd[i]=1表示第i位为素数,为0就表示这一位是合数,还有千万别用memset搞int数组除了0或-1以外的数
pd[1]=0;//1既不是素数,也不是合数,很重要
cin>>n>>m;
prime();
for(int i=1;i<=m;i++)
{
scanf("%d",&x);
if(pd[x]==1) puts("Yes");
else puts("No");
}
return 0;
}
//总之,在这个方法中,每个数至少被访问一次,每个数至多被访问一次。对于质数,一定会在i的循环中访问到,并确定为质数。对于合数,一定可以分解为一个最小素因子和其他数的乘积。
.
.
.
来说说最难理解的一一句代码:
if(i%pr[j]==0) break;
说明一下我对这句代码的理解,可以先设x=m*a,其中m是x的最小质因数。a是x的最大因数,但不确定是质数还是合数。那么按照那句代码,如果a是质数,在循环结束之前都不可能找到能让它退出循环的那个数。如果a是合数的话,就有a=P1*P2*P3……*Pn,各个Pi是a分解质因数后的各个质数(其中p1是a的最小质因数).
现在假设有一个质数y满足 a%y==0 ,那么必然有y>=p1,那么显然y+1>p1,此时(y+1)*a这个数不可以被a筛掉,因为必然有[(y+1)*P2*P3……*Pn] > (P1*P2*P3……*Pn), 此时(y+1)*a这个数的最小质因数应该是p1才对,要是在这不退出循环,那么一个数就可能会被很多组的数筛选掉,虽然这里不退出循环也是对的,但是就会导致时间复杂度退化。
.
.
.
然后呢,第一种打法的时间复杂度虽然是O(n),但是它的常数比较的大,其实还有一种打法,更快那么一点点。
//第二种打法
#include
#include
#include
using namespace std;
int n,m,pr[10000010],x,tot=0,mx[10000010],pd[10000010];
void prime()
{
for(int i=2;i<=n;i++)
{
if(pd[i]==1)
{
pr[++tot]=i;
mx[i]=tot;//mx[i]保存第i位数的最小质因数
}
for(int j=1;j<=mx[i]&&pr[j]*i<=n;j++)
{
pd[i*pr[j]]=0;//标记为合数
mx[i*pr[j]]=j;//再保存被它筛选出来的数的最小质因数,以便下次使用
}
}
}
int main()
{
cin>>n>>m;
for(int i=1;i<=10000009;i++) pd[i]=1;
prime();
pd[1]=0;
for(int i=1;i<=m;i++)
{
scanf("%d",&x);
if(pd[x]==1) puts("Yes");
else puts("No");
}
return 0;
}
//说一下为什么第二种会快,因为第一种打法需要很多次取余数操作来确定是否就退出循环,比较耗时。第二种打法呢可以直接确定边界,循环到那里就行。这样时间复杂度的那个常数会较小一点。