线性筛法详解:从筛质数到欧拉函数、Möbius 函数与约数函数
一、线性筛解决什么问题
普通的埃氏筛可以在较低复杂度内筛出所有质数,但一个合数可能被多个质数重复筛掉。
线性筛,也叫 Euler 筛,其核心目标是:
让每一个合数只被它的最小质因子筛掉一次。
因此总复杂度可以做到:
O(n) O(n) O(n)
更重要的是,线性筛不仅可以求质数,还可以在筛的过程中同时递推出大量数论函数,例如:
- 欧拉函数 φ(n)\varphi(n)φ(n);
- Möbius 函数 μ(n)\mu(n)μ(n);
- 约数个数 d(n)d(n)d(n);
- 不同质因子个数 ω(n)\omega(n)ω(n);
- 最小质因子;
- 最小质因子的指数;
- 其他积性函数。
因此真正值得掌握的不是某一份代码,而是:
已知 iii 的信息,如何根据 p∣ip\mid ip∣i 和 p∤ip\nmid ip∤i 两种情况递推出 ipipip 的信息。
二、最基本的线性筛
核心代码如下:
cpp
const int N=1e7+100;
int primes[N];
int pcnt;
bool isComposite[N];
void sieve(){
for(int i=2;i<N;i++){
if(!isComposite[i]){
primes[pcnt++]=i;
}
for(int j=0;j<pcnt&&1LL*i*primes[j]<N;j++){
int p=primes[j];
isComposite[i*p]=true;
if(i%p==0)
break;
}
}
}
其中:
cpp
if(!isComposite[i])
说明 iii 从来没有被更小的质数筛掉,因此 iii 本身是质数。
而最关键的一句是:
cpp
if(i%p==0)
break;
三、为什么一定要 break
设当前枚举:
cpp
i
以及质数:
cpp
p
并考虑合数:
x=ip x=ip x=ip
线性筛要求 xxx 只由它的最小质因子筛掉。
当第一次出现:
p∣i p\mid i p∣i
时,ppp 已经是 iii 的最小质因子。
如果继续枚举更大的质数 qqq,得到:
iq iq iq
那么 iqiqiq 的最小质因子仍然是 ppp,而不是 qqq。
因此 iqiqiq 应该在之后由 ppp 筛掉,而不应该在这里由 qqq 筛掉。
所以必须:
cpp
if(i%p==0)
break;
正是这句代码保证:
每一个合数只被它的最小质因子处理一次。
因此总复杂度为:
O(n) O(n) O(n)
四、线性筛最重要的分类讨论
在线性筛中,我们始终处理:
x=ip x=ip x=ip
其中 ppp 是质数。
接下来只有两种情况。
情况 1:p∤ip\nmid ip∤i
此时:
gcd(i,p)=1 \gcd(i,p)=1 gcd(i,p)=1
相当于给 iii 加入了一个全新的质因子 ppp。
对于积性函数,通常可以直接利用:
f(ip)=f(i)f(p) f(ip)=f(i)f(p) f(ip)=f(i)f(p)
情况 2:p∣ip\mid ip∣i
此时 ppp 已经是 iii 的质因子。
若:
i=pam,p∤m i=p^am,\quad p\nmid m i=pam,p∤m
那么:
ip=pa+1m ip=p^{a+1}m ip=pa+1m
也就是说:
质因子的种类没有发生变化,只是 ppp 的指数增加了 1。
很多数论函数的递推就是根据这两个情况得到的。
五、线性筛求欧拉函数
欧拉函数定义:
$$
\varphi(n)
#{1\le x\le n:\gcd(x,n)=1}
对于质数 ppp: φ(p)=p−1 \\varphi(p)=p-1 φ(p)=p−1 因此发现质数时: ```cpp phi[i]=i-1; ``` *** ** * ** *** ### 1. 当 p∤ip\\nmid ip∤i 因为: gcd(i,p)=1 \\gcd(i,p)=1 gcd(i,p)=1 利用欧拉函数的积性: ##
\varphi(ip)
\varphi(i)\varphi§
所以: φ(ip)=φ(i)(p−1) \\boxed{ \\varphi(ip)=\\varphi(i)(p-1) } φ(ip)=φ(i)(p−1) 代码: ```cpp phi[i*p]=phi[i]*(p-1); ``` *** ** * ** *** ### 2. 当 p∣ip\\mid ip∣i 设: i=pam i=p\^am i=pam 其中: p∤m p\\nmid m p∤m 根据欧拉函数公式: φ(pa)=pa−1(p−1) \\varphi(p\^a)=p\^{a-1}(p-1) φ(pa)=pa−1(p−1) 所以再乘一个 ppp 后: ##
\varphi(p^{a+1})
p\varphi(p^a)
因此: φ(ip)=pφ(i) \\boxed{ \\varphi(ip)=p\\varphi(i) } φ(ip)=pφ(i) 代码: ```cpp phi[i*p]=phi[i]*p; ``` 所以欧拉函数完整转移为: ```cpp if(i%p==0){ phi[i*p]=phi[i]*p; break; } else{ phi[i*p]=phi[i]*(p-1); } ``` *** ** * ** *** ## 六、线性筛求 Möbius 函数 Möbius 函数定义: μ(n)={1,n=10,n 含平方质因子(−1)r,n 是 r 个不同质数的乘积 \\mu(n)= \\begin{cases} 1,\&n=1\\\\ 0,\&n\\text{ 含平方质因子}\\\\ (-1)\^r,\&n\\text{ 是 }r\\text{ 个不同质数的乘积} \\end{cases} μ(n)=⎩ ⎨ ⎧1,0,(−1)r,n=1n 含平方质因子n 是 r 个不同质数的乘积 对于质数: μ(p)=−1 \\mu(p)=-1 μ(p)=−1 所以: ```cpp mu[i]=-1; ``` *** ** * ** *** ### 1. 当 p∤ip\\nmid ip∤i 此时增加了一个新的质因子,因此不同质因子数量奇偶性改变: μ(ip)=−μ(i) \\boxed{ \\mu(ip)=-\\mu(i) } μ(ip)=−μ(i) *** ** * ** *** ### 2. 当 p∣ip\\mid ip∣i 此时: p2∣ip p\^2\\mid ip p2∣ip 因此 ipipip 含平方质因子: μ(ip)=0 \\boxed{ \\mu(ip)=0 } μ(ip)=0 代码: ```cpp if(i%p==0){ mu[i*p]=0; break; } else{ mu[i*p]=-mu[i]; } ``` *** ** * ** *** ## 七、线性筛求不同质因子个数 定义: ω(n) \\omega(n) ω(n) 表示 nnn 中不同质因子的数量。 例如: 12=22×3 12=2\^2\\times3 12=22×3 所以: ω(12)=2 \\omega(12)=2 ω(12)=2 对于质数: ω(p)=1 \\omega(p)=1 ω(p)=1 *** ** * ** *** 当: p∤i p\\nmid i p∤i 加入了一个新的质因子: ω(ip)=ω(i)+1 \\boxed{ \\omega(ip)=\\omega(i)+1 } ω(ip)=ω(i)+1 当: p∣i p\\mid i p∣i ppp 本来已经存在: ω(ip)=ω(i) \\boxed{ \\omega(ip)=\\omega(i) } ω(ip)=ω(i) 代码: ```cpp if(i%p==0){ omega[i*p]=omega[i]; break; } else{ omega[i*p]=omega[i]+1; } ``` *** ** * ** *** ## 八、线性筛求最小质因子 由于线性筛中一个合数总是由最小质因子筛掉,因此求最小质因子非常自然。 对于质数: ```cpp minPrime[i]=i; ``` 处理: x=ip x=ip x=ip 时,由于 `primes` 从小到大枚举,当前 ppp 就是 xxx 的最小质因子: ```cpp minPrime[x]=p; ``` *** ** * ** *** ## 九、最小质因子的指数 设: n=pam n=p\^am n=pam 其中 ppp 是 nnn 的最小质因子,并且: p∤m p\\nmid m p∤m 我们记录: ```cpp minPrimeExp[n]=a; ``` 对于质数: ```cpp minPrimeExp[p]=1; ``` *** ** * ** *** 如果: p∣i p\\mid i p∣i 说明继续增加同一个最小质因子: minPrimeExp\[ip\]=minPrimeExp\[i\]+1 \\boxed{ minPrimeExp\[ip\]=minPrimeExp\[i\]+1 } minPrimeExp\[ip\]=minPrimeExp\[i\]+1 如果: p∤i p\\nmid i p∤i ppp 是新出现的最小质因子,因此指数为 1: minPrimeExp\[ip\]=1 \\boxed{ minPrimeExp\[ip\]=1 } minPrimeExp\[ip\]=1 *** ** * ** *** ## 十、线性筛求约数个数 设: n=p1a1p2a2⋯prar n=p_1\^{a_1}p_2\^{a_2}\\cdots p_r\^{a_r} n=p1a1p2a2⋯prar 则正约数个数: ##
d(n)
(a_1+1)(a_2+1)\cdots(a_r+1)
对于质数: d(p)=2 d(p)=2 d(p)=2 *** ** * ** *** ### 1. 当 p∤ip\\nmid ip∤i 增加一个指数为 1 的新质因子,因此: d(ip)=2d(i) \\boxed{ d(ip)=2d(i) } d(ip)=2d(i) *** ** * ** *** ### 2. 当 p∣ip\\mid ip∣i 设: i=pam i=p\^am i=pam 其中: p∤m p\\nmid m p∤m 那么: d(i)=(a+1)d(m) d(i)=(a+1)d(m) d(i)=(a+1)d(m) 而: d(ip)=(a+2)d(m) d(ip)=(a+2)d(m) d(ip)=(a+2)d(m) 因此: ##
\boxed{
d(ip)
d(i)\frac{a+2}{a+1}
}
$$
若 minPrimeExp[i]=a,代码就是:
cpp
divisorCnt[i*p]
=
divisorCnt[i]
/
(minPrimeExp[i]+1)
*
(minPrimeExp[i]+2);
十一、综合模板
cpp
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1e7+100;
int primes[N];
int pcnt;
bool isComposite[N]; // 是否为合数
int phi[N]; // 欧拉函数
int mu[N]; // Möbius 函数
int divisorCnt[N]; // 正约数个数
int omega[N]; // 不同质因子个数
int minPrime[N]; // 最小质因子
int minPrimeExp[N]; // 最小质因子的指数
void sieve(){
phi[1]=1;
mu[1]=1;
divisorCnt[1]=1;
for(int i=2;i<N;i++){
if(!isComposite[i]){
primes[pcnt++]=i;
phi[i]=i-1;
mu[i]=-1;
divisorCnt[i]=2;
omega[i]=1;
minPrime[i]=i;
minPrimeExp[i]=1;
}
for(int j=0;j<pcnt&&1LL*i*primes[j]<N;j++){
int p=primes[j];
int x=i*p;
isComposite[x]=true;
minPrime[x]=p;
if(i%p==0){
phi[x]=phi[i]*p;
mu[x]=0;
omega[x]=omega[i];
minPrimeExp[x]=minPrimeExp[i]+1;
divisorCnt[x]
=
divisorCnt[i]
/
(minPrimeExp[i]+1)
*
(minPrimeExp[i]+2);
break;
}
else{
phi[x]=phi[i]*(p-1);
mu[x]=-mu[i];
omega[x]=omega[i]+1;
minPrimeExp[x]=1;
divisorCnt[x]=divisorCnt[i]*2;
}
}
}
}
int main(){
sieve();
return 0;
}
十二、一个很容易出现的下标错误
如果使用:
cpp
primes[++pcnt]=i;
说明质数从:
cpp
primes[1]
开始存。
那么枚举时也必须:
cpp
for(int j=1;j<=pcnt;j++)
不能从 j=0 开始。
否则:
cpp
primes[0]=0
后面可能出现:
cpp
i%primes[0]
也就是:
cpp
i%0
直接 Runtime Error。
更推荐统一使用 0 下标:
cpp
primes[pcnt++]=i;
然后:
cpp
for(int j=0;j<pcnt;j++)
这样最不容易出错。
十三、总结
线性筛真正需要掌握的是:
x=ip x=ip x=ip
之后分类:
p∤i p\nmid i p∤i
和:
p∣i p\mid i p∣i
两种情况。
可以简单理解为:
- p∤ip\nmid ip∤i:加入一个全新的质因子;
- p∣ip\mid ip∣i:已有质因子的指数增加 1。
因此很多积性函数都可以在线性筛中一起递推。
常见结论:
欧拉函数
φ(ip)={φ(i)(p−1),p∤ipφ(i),p∣i \varphi(ip)= \begin{cases} \varphi(i)(p-1),&p\nmid i\\ p\varphi(i),&p\mid i \end{cases} φ(ip)={φ(i)(p−1),pφ(i),p∤ip∣i
Möbius 函数
μ(ip)={−μ(i),p∤i0,p∣i \mu(ip)= \begin{cases} -\mu(i),&p\nmid i\\ 0,&p\mid i \end{cases} μ(ip)={−μ(i),0,p∤ip∣i
不同质因子个数
ω(ip)={ω(i)+1,p∤iω(i),p∣i \omega(ip)= \begin{cases} \omega(i)+1,&p\nmid i\\ \omega(i),&p\mid i \end{cases} ω(ip)={ω(i)+1,ω(i),p∤ip∣i
约数个数
若 p∤ip\nmid ip∤i:
d(ip)=2d(i) d(ip)=2d(i) d(ip)=2d(i)
若:
i=pam i=p^am i=pam
则:
d(ip)=d(i)a+2a+1 d(ip)= d(i)\frac{a+2}{a+1} d(ip)=d(i)a+1a+2
所以以后看到"要求预处理 1∼n1\sim n1∼n 中大量数论函数值"的题目,可以优先考虑:
这个函数是不是积性函数?能不能在线性筛处理 ipipip 时递推出来?
这比单纯背一份 Euler 筛代码更重要。