线性筛法求素数

普通筛法

先讲一下普通的筛法。筛法,顾名思义,就是去掉合数,剩下的就是素数了。
我们知道,合数一定可以分解为两个或以上的素数,所以我们只需要对于每一个素数 i ,枚举一个大于2的数 j ,将 ij (此为某个合数)筛掉。如果 i 是合数,那一定会被它的一个质因数乘上某个数而被筛掉,反之,他将不会被筛掉。

Code(pascal) normal

    for i:=2 to n do
    if bz[i] then  //如果此数没有被小于它的质数乘上某个数所筛掉,说明它是质数
    for j:=2 to n div i do  //枚举i的乘数j
    bz[i*j]:=false;  //筛掉此数

这个算法的时间复杂度是 n2 的,我们看一下能不能优化一下。
我们可以发现,对于一个大于 n 的质数,我们可以发现用此数来筛是没有意义的。
我们设这个数为 y ,则 j 一定小于 y (因为 y >√ n , j 最大为 n/y ,所以 j <√ n < y )
设j= P1P2P3...Pn(Pi) ,因为 j < y ,所以 Pi < y ,所以 jy=Piu()

综上所述,当 i=Pi ( Pi<y )时, jy y>n )这个合数被 Piu 筛掉了,所以只需循环到√ n ,且当 j<i 时,筛 ij 没有意义( ij 已经被筛),所以 i 只需循环到√ n ,且枚举 j 时,可以从i开始枚举。

Code(pascal) optimization

    for i:=2 to trunc(sqrt(n)) do
    if bz[i] then  //判断质数
    for j:=i to n div i do  //枚举i的乘数j(j≥i)
    bz[i*j]:=false;  //筛数

线性筛法

现在开始讲主角了,先看看代码。

Code(pascal) linear

for i:=1 to n do
begin
    if bz[i]=false then  //i为素数则bz[i]=false,为合数则bz[i]=true
    begin
        inc(o);
        s[o]:=i;  //s为素数数组
    end;
    for j:=1 to o do
    begin 
        bz[i*s[j]]:=true;
        if (i mod s[j]=0) or (i*s[j]>n) then break;  //难点???重点!!!超强优化!?!?!? 
    end;
end;

重点在于为什么i mod s[j]就不需要筛了?
事实上我们这样每个合数只会被筛一遍。
接下来我们需要证明两个东西:
1、质数一定不会被筛。
2、合数一定会被筛,且只会被筛一次。
我们知道质数只有一个质因数,因此,所以不会被两个大于1的数的乘积所筛。
对于合数,我们设合数 K=P1P2P3...Pm1PmPwPw+1
所以使得循环break掉的素数就是 P1 ,所以比 P1 大的质数(设其为 U ),不会在此时把 iU 筛掉,但是 iU 一定会被筛掉.
(iU)=P1P2P3...Pm1PmU(U>P1)
=UP2P3...Pm1PmP1
=iP1(i>i)
又因为 i 没有比 P1 要小的质因数,所以当 i=i ,一定会枚举到 P1 此素数将 P1iiU 筛掉。
通过上面我们可以知道,每个合数只会被自己最小的质因数乘上某数筛掉,因此只会被筛一次且一定被筛,所以时间复杂度就降至了 O(n)

你可能感兴趣的:(线性素数筛法)