线性筛

线性筛用在素数、欧拉函数、莫比乌斯函数的打表上。

埃拉托斯特尼筛法

一开始最容易理解的筛法是酱紫滴~

#define N 1000100
#define LL long long
int num[N], prim[N];
int pn = 0;
void table(){
    memset(num, -1, sizeof(num));
    for(int i = 2;i < N;i++) if(num[i]){
        prim[pn++] = i;
        for(LL j = 1LL*i*i;j < N;j += i) if(num[j])
            num[j] = 0;
    }
}

注意在标记i的倍数只需从i*i开始,因为小于它的i的倍数势必已经被更小的数作为倍数筛掉。同时小心i*iint。但是这个写法会让一些数被重复筛。如30,会被2、3、5作为倍数筛三遍,这样的访问太多余了。

欧拉筛法

这个筛法做到了每个数只被筛一遍。

#define N 100100000
#define LL long long
int num[N], prim[N];
int pn = 0;
void table(){
    memset(num, -1, sizeof(num));
    for(int i = 2;i < N;i++){
        if(num[i]) prim[pn++] = i;
        for(int j = 0;j < pn && 1LL*i*prim[j] < N;j++){
            num[i*prim[j]] = 0;
            if(i % prim[j] == 0) break;
        }
    }
}

全篇的精华在于:

if(i % prim[j] == 0) break;

这个break保证了合数只被最小质约数访问到。
比如40=2*20=4*10=5*8,只有i=20时,才会在prim[j]=2的时候被访问到。
i=10时,在prim[j]=2时就已经被break了;同样的,i=8时,在prim[j]也已经break了。
敢于这样break的原因在于,当i % prim[j] == 0 (prim[j])2n ,则在 i=nprim[j] 时,自然会与prim[j]共同来标记 n(prim[j])2 ,而且仅有最小的prim[j]会标记它,这样就保证了不重不漏。

附赠打表

来优雅地打个表

不,是下面这个…

充分利用了欧拉函数、莫比乌斯函数作为积性函数的性质,灰常好写~

#define N 100100
#define LL long long
int num[N], prim[N], phi[N] = {1,1}, mob[N]={1,1};
int pn = 0;
void table(){
    memset(num, -1, sizeof(num));
    for(int i = 2;i < N;i++){
        if(num[i]) {
            prim[pn++] = i;
            phi[i] = i-1;
            mob[i] = -1;
        }
        for(int j = 0;j < pn && 1LL*i*prim[j] < N;j++){
            if(i % prim[j] == 0){
                phi[i*prim[j]] = phi[i] * prim[j];
                num[i*prim[j]] = 0;
                mob[i*prim[j]] = 0;
                break;
            }
            phi[i*prim[j]] = phi[i] * (prim[j]-1);
            num[i*prim[j]] = 0;
            mob[i*prim[j]] = -mob[i];
        }
    }
}

你可能感兴趣的:(--数学)