素数筛法的常数优化简单整理

个人整理, 转载请注明出处,thx.

最普通的线性筛法, earthson的代码比较优秀了
比赛的时候很少有同时卡筛法时间和空间的。

线性筛法的定义凭我自己理解就是对每个范围内的数每个合数标记一次且仅一次,没标记的就是素数,这样遍历每个数的次数就是一个常数,因此总时间是线性的。

常用的线性筛法大多考虑的是每个合数都会拆成一个它最小的质因子乘以某个数,所以有2种方法:

1.通过枚举每个已判别的素数p来筛掉以它为最小质因子的合数,通常从p*p到N。

2.通过枚举每个数将它与已筛出的素数顺序相乘直到该数能整除某个素数为止(这样保证了所乘素数是最小的质因子)。

这个范围可以是1到N,也可以是(2, N]间的奇数(当然记录素数时要加上2),也可以是(3,N] 间的(不能整除3的奇数(earthson给出了一个))
还有一个小技巧是位模式标记, 因为c/c++中bool 型都是一个字节的,如果换成1个int存32个标记位,再利用位运算,空间上会省很多大约是原来的1/8,时间上也不会多耗费多少。

 另外还有一个省空间的小技巧是将筛表循环使用,重复覆盖原来的筛表(这个不再这里赘述)。


只筛奇数的情况, 生成一个isprime的bool数组,不能表示2,但是也可以理解没isprime[1]同时表示了2和3,不过这样做没什么意义。

/// N=100000000  runtime=1.765000s

bool isprime[N>>1]={0}; /// half of the upper
void init_prime(){/// not contain 2.
    const int lim =sqrt(N+1)/2; /// N is the upper limit of primer
    const int maxp=(N+1)>>1;
    for(int i=1; i<=lim; ++i){
        int j=i<<1|1;
        if(!isprime[i]){
            for(int k=(i<<1)*(i+1); k<=maxp; k+=j){
               /// i*2+1 is prime, k=(i*2+1)*(i*2+1)/2 (note that k is odd)
                /// note that k*2+j*2*x is even, so we may only judge k*2+j*(2*x+1) in [2i*i+2i,maxp];
                isprime[k] = 1 ;/// k*2+1 isn't prime, so mark it!
            }
        }
    }
}



/// 100000000 1.375000s/// 100000000 1.375000s

对于生成prime数组, earshson的代码修改的, 也是只筛奇数, 手动添加2到prime里就可以了,用到的是第二种方法,额外开了一个数组保存素数,明显快一些

bool isprime[N>>1]={0}; /// N is the upper limit of primer
int prime[6000000]; /// maxn is more than prime[0]
int init_prime1()
{
    isprime[0]=1;/// 1 is not a primer
    prime[prime[0]=1]=2; /// 2 handmake
    int lim=N>>1;
    for(int i=1; i<lim; ++i)/// i<=lim st. 100000001 is a primer ,however that is not the case.
    {
        int j =i<<1|1;
        if(!isprime[i])prime[++prime[0]]=j;
        for (int k=2; k<=prime[0] && prime[k]*j<=N; ++k)
        {
            isprime[prime[k]*j>>1]=1;
            if(j%prime[k]==0)break;
        }
    }
    return prime[0];
}

 

/// 100000000 0.859000s 0.875000s

这个是earthson的另一份代码

/*过滤掉了所有的2的倍数和3的倍数,所以筛表的大小变为原来的1/3(1/2*2/3),时间上也近似为原来的1/3。不过考虑到循环内的运算增多,其实没那么快。使用位模式标记晒表的状态,所以总的空间为N/96 + 1。*/

 nu换成递推的能节省不少时间

int primes[PRIME_LIM] = {0};
int flags[N/96 + 1] = {0};

int get_prime()
{
    int nu = 5, to = 0;
    primes[0] = 2;
    primes[1] = 2, primes[2] = 3;
    for(int i = 0; nu <= N; i++)
    {
        if(!(flags[i>>5]&(1<<(i&31)))) primes[++primes[0]] = nu;
        for(int j = 3; j <= primes[0] && primes[j] * nu <= N; j++)
        /// 考虑到素数分布的情况, 不会有对N=10^8 使循环再break之前溢出
        {
            to = (nu * primes[j] - 5) >> 1;
            to -= to/3;
            flags[to>>5] |= 1<<(to&31);
            if(!(nu % primes[j])) break;
        }
        nu += 2 + ((i&1) << 1);
    }
    return primes[0];
}


 

你可能感兴趣的:(优化)