线性筛用在素数、欧拉函数、莫比乌斯函数的打表上。
埃拉托斯特尼筛法
一开始最容易理解的筛法是酱紫滴~
#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*i
爆int
。但是这个写法会让一些数被重复筛。如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])2∣n
,则在
i=n∗prim[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];
}
}
}