素数(质数)的判断:
一.直接判断某一个数n是否为素数:
bool check(int n)
{
if(i == 1) return 0;
for(int i = 2;i <= sqrt(n); i++)
if(n % i == 0) return 0;
return 1;
}
二.判断多个素数(或求区间素数个数)
另:不大于x的质数个数记为π(x),π(x)约为 x ÷ ln(x)
就以 求
[
1
,
n
]
[1,n]
[1,n]的质数个数 为例
正常枚举:
T
=
O
(
n
n
)
T = O(n \sqrt n)
T=O(nn)
考虑进行优化,与其挨个枚举,不如考虑质数合数的本质区别:合数有除1与本身的因数,而质数没有。所以我们使某个数乘大于1的数得到的数就可以得到一个合数并对其进行标记。
1.埃氏筛法(Eratosthenes)
思想:
质数的倍数一定不是质数。
基本方法:
初始化所有数为质数,从2开始找到每个质数,并枚举它的倍数将它们记为合数。由于某个合数的因数一定小于它本身,所以该合数一定被前面的数标记过了,由此达成边标边判断。
小优化:
类比于正常枚举判断质数的思想,一个数y的因子除了可能存在的 y \sqrt y y外一定是成对出现并分布在 y \sqrt y y两侧,我们只需要用小于 y \sqrt y y一侧的因子对其进行标记就好了,即只用x满足 x < y \sqrt y y来标记。对于x来说则是要标记的数 y > x 2 y > x ^2 y>x2
bool ispri[N];//ispri[i] = 1 i是合数
int pri[(N /log(N)) << 1],cnt = 0;//不大于x的质数个数约为 x ÷ ln(x)
void prime(int n)
{
for(int i = 2;i <= n; i++)
{
if(!ispri[i]) pri[++cnt] = i;
for(int j = i;j <= n / i; j++)
ispri[i * j] = 1;
}
}
2.线性筛法(另计:Segmentation fault 通常为数组越界或链表地址无法调用)
思想:
某个 含有除自己以外的质因数 的数一定不是质数。
基本方法:
因为哪怕是埃氏筛,也会出现
12
=
2
×
6
=
3
×
4
12=2 \times 6=3 \times 4
12=2×6=3×4这样的重复排除,归根结底是拆分不彻底,导致情况组合数增加。
所以我们要求只用质因数来得到,毕竟对于任意一个数,它的质因数分解是一定的。我们存储每个数的最小质因数。然后每次向后枚举时都乘以目前存储的质数,但是遇到自己的质因数时就停止。否则就继续取质数出来标记,这样就可以保证这次标记一定不含被跳过的质数,用本次标记的质数的下一个质数标记时一定不会重复,遇到自己质因数及时停止则是防止这一次要标记的数下一次被其他质数标记。
稍微梳理一下:
p
r
i
[
]
pri[]
pri[]数组已经存储了目前
[
1
,
i
]
[1,i]
[1,i]找到的质数
for(int j = 1;j <= cnt; j++)//找质数出来
对于i有两种情况:
先进行首次标记:ispri[i * pri[j]] = 1;
@1 i % pri[j] == 0 及时跳出
@2 i % pri[j] != 0 i里面的质因数一定没有pri[j],那么用pri[j + 1] 来标记的时候一定不会与pri[j]重复标记
完整代码:
//ispri[i] = 1 i不是质数
void getpri(int n)
{
ispri[1] = 1;
pri[1] = 1;
for(int i = 2;i <= n; i++)
if(!ispri[i]) pri[++cnt] = i;
for(int j = 1;j <= cnt and pri[j] * j <= n; j++)
{
ispri[i * pri[j]] = 1;
if(i % pri[j] == 0) break;//pri[j]能合成i,i是合数,找到最小质因数便停止,防止之后的数被多个因数同时标记
}