【瞎口胡】基础数学 3 筛法
写在前面
还记得第一篇的内容吗?不记得了可以去复习一下。
这一篇将会用到它们。
Eratosthenes 筛法
译名埃拉托色尼筛法。用于筛选 \([1,n]\) 范围内的素数。
其算法思想是,一个质数的所有倍数都不是质数。每当我们筛选到一个质数 \(k\) 时,我们就把 \(xk (x>1 \and xk \leq n)\) 标记为合数。
bool vis[500010];
int n;
...
scanf("%d",&n);
for(int i=2;i<=n;++i){
if(!vis[i]){
for(int j=1;i*j<=n;++j){
vis[i*j]=true;
}
}
}
// 注意 1 不是质数,因此不能简单地用 !vis[x] 来判断 x 是否为质数,应为 !vis[x]&&x!=1
其时间复杂度为 \(O(n \log \log n)\),近似于线性。证明较为复杂,感兴趣的读者可以自行查阅。
但我们仍然有严格 \(O(n)\) 的算法,这就是我们这篇文章的重点:线性筛。
线性筛法 / 欧拉筛法
质数筛
Eratosthenes 不够优秀的原因之一是一个合数可能被它的多个质因子筛掉。为此,我们引入线性筛法,对于每个合数,它只会被自己的最小质因子筛掉,因此是 \(O(n)\)。
假设我们已经知道了 \(\leq i\) 的正整数中,每个数是质数与否。令 \(vis_i\) 表示某个数是否被标记为质数(\(vis_i=1\) 即为合数,反之则为质数),\(\operatorname{prime}_{1},\operatorname{prime}_2,...,\operatorname{prime}_s\) 表示 \(\leq i\) 的质数序列。不妨假设 \(\operatorname{prime}\) 序列单调递增。
对于这样的 \(i\),我们依次将 \(vis_{i \times \operatorname{prime}_j}(i \times \operatorname{prime}_j \leq n \and 1 \leq j \leq s)\) 标记为 \(1\)。如果在这个过程中发现存在某个 \(j\) 使得 \(\operatorname{prime}_j \mid i\),则不再进行后续的标记。
这样做的正确性是有保证的。假设出现 \(\operatorname{prime}_j \mid i\) 时有 \(j=v\),那么有:
-
所有 \(j' \leq v\)(注意不是 \(<\))都满足 \(\operatorname{prime}_j'\) 是 \(i \times \operatorname{prime}_{j'}\) 的最小质因子。
反证法:如果 \(i\) 有比 \(\operatorname{prime}_{j'}\) 更小的质因子,那么当时就会停止,不会再进行标记。
-
所有 \(j'>v\) 都满足 \(\operatorname{prime}_v\) 是 \(i \times \operatorname{prime}_{j'}\) 的最小质因子。
因为有 \(\operatorname{prime}_v \mid i\),而 \(\operatorname{prime}_v<\operatorname{prime}_{j'}\)。
这样,被标记的合数一定是被它的最小质因子标记一次。对每个 \(i(2 \leq i \leq n)\) 进行上述操作,就得到了 \(\leq n\) 的正整数中,每个数是质数与否。
而怎样确保一个合数一定被标记过呢?设任意合数 \(x\) 的最小质因子为 \(p\)。观察到,它一定会在 \(i=\dfrac{x}{p}\) 时被筛掉。因为 \(p\) 是 \(x\) 的最小质因子,那么 \(p\) 一定不大于 \(\dfrac{x}{p}\) 的最小质因子,故而在 \(i = \dfrac xp\) 时,不会提前终止标记,一定会标记 \(x\) 为合数。
for(int i=2;i<=n;++i){
if(!vis[i]){
prime[++sum]=i;
}
for(int j=1;i*prime[j]<=n&&j<=sum;++j){
vis[i*prime[j]]=true;
if(i%prime[j]==0){
break;
}
}
}
小练习
Statement
求 \([l,r]\) 中素数的个数,其中 \(1 \leq l \leq r \leq 2^{31}-1,r-l \leq 10^6\)。
Solution
观察到 \(x\) 一定存在一个 \(\leq \sqrt x\) 的因子。这启发了我们 —— 我们只需要找到 \(\leq \sqrt {2^{31}-1}\) 的所有质数,然后用这些质数筛掉 \([l,r]\) 的所有合数。
有一个重要的结论:\(n\) 以内的质数个数约为 \(\dfrac{n}{\ln n}\) 个,当 \(n=\left \lfloor \sqrt {2^{31}-1} \right \rfloor\) 时该式约为 \(7 \times 10^3\)。因此复杂度为 \(O(\dfrac{r-l+1}{2}+\dfrac{r-l+1}{3}+\dfrac{r-l+1}{5}...) \leq O((r-l+1) \log (r-l+1))\),可以通过本题。
Code
# include <bits/stdc++.h>
# define int long long
const int N=50010,INF=0x3f3f3f3f;
int l,r;
int n;
int prime[N],cnt;
bool vis[N];
bool larvis[1000010];
inline int read(void){
int res,f=1;
char c;
while((c=getchar())<'0'||c>'9')
if(c=='-')f=-1;
res=c-48;
while((c=getchar())>='0'&&c<='9')
res=res*10+c-48;
return res*f;
}
inline void calc(void){
for(int i=2;i<=N-10;++i){
if(!vis[i]){
prime[++cnt]=i;
}
for(int j=1;i*prime[j]<=N-10;++j){
vis[i*prime[j]]=true;
if(i%prime[j]==0){
break;
}
}
}
return;
}
# undef int
int main(void){
# define int long long
// freopen("pcount.in","r",stdin);
// freopen("pcount.out","w",stdout);
l=read(),r=read();
calc();
printf("%lld ",cnt);
for(int i=1;i<=cnt;++i){
int p=l/prime[i];
if(l%prime[i]){
++p;
}
p=std::max(p,2ll);
for(;p*prime[i]<=r;++p){
larvis[p*prime[i]-l+1]=true;
}
}
int ans=0;
for(int i=1;i<=r-l+1;++i){
ans+=(int)(!larvis[i]);
}
printf("%lld",ans-(l==1ll));
return 0;
}
约数个数 Tau 函数
\(\tau(n)\) 表示 \(n\) 的正约数个数。
\(\tau(n) = \prod\limits_{i=1}^{m} (k_i+1)\),其中 \(n = \prod\limits_{i=1}^{m} p_i^{k_i}\)。
在线性筛的过程中,设当前枚举到的数为 \(q = i \times prime_{j}\)。对于 \(n=1\) 和 \(n\) 为质数的情况是平凡的,稍加处理即可。
-
如果 \(prime_j \nmid i\):
按照计算式,\(\tau(q) = \tau(i) \times 2\)。
-
如果 \(prime_j \mid i\):
可以这样想,\(q\) 一定只比 \(i\) 多了「 \(i\) 的所有因子中 \(prime_j\) 次数最大的因子个数」个因子。观察到这样就相当于把 \(prime_j\) 的次数固定了,所以我们不考虑 \(prime_j\) 的次数,即将它看作 \(0\) 次。
再分类讨论:
- 如果 \(i\) 中只有一个 \(prime_j\),\(\tau(q) = \tau(i) + \tau(\dfrac{i}{prime_j})\),即将这个 \(prime_j\) 除掉之后 \(prime_j\) 就变为了 \(0\) 次。
- 如果 \(i\) 中有大于一个 \(prime_j\),那么递归地想,\(q\) 比 \(i\) 多的因子个数和 \(i\) 比 \(\dfrac{i}{prime_j}\) 多的因子个数相同。\(i\) 比 \(\dfrac{i}{prime_j}\) 多 \(\tau(i) - \tau(\dfrac{i}{prime_j})\) 个因子。因此 \(\tau(q) = \tau(i) + (\tau(i)-\tau(\dfrac{i}{prime_j}))\)。
观察到第一种情况中 \(\tau(i) = 2\tau(\dfrac{i}{prime_j})\),仍然可以写作第二种情况。因此我们简化了代码。
inline void Tao(void){ tao[1]=1; for(int i=2;i<=MLIM;++i){ if(!vis[i]){ prime[++sum]=i; tao[i]=2; // 质数的处理 } for(int j=1;i*prime[j]<=MLIM&&j<=sum;++j){ vis[i*prime[j]]=true; if(i%prime[j]==0){ tao[i*prime[j]]=tao[i]+(tao[i]-tao[i/prime[j]]); break; }else{ tao[i*prime[j]]=tao[i]*2; } } } for(int i=1;i<=LIM;++i){ printf("%lld ",tao[i]); } return; }
约数和 Sigma 函数
\(\sigma(n)\) 表示 \(n\) 的正约数和。
\(\sigma(n) = \prod\limits_{i=1}^{m} (\sum \limits_{j=0}^{k_i} p_i^j)\),其中 \(n = \prod\limits_{i=1}^{m} p_i^{k_i}\)。
和 \(\tau\) 的筛法类似。
在线性筛的过程中,设当前枚举到的数为 \(q = i \times prime_{j}\)。对于 \(n=1\) 和 \(n\) 为质数的情况是平凡的,稍加处理即可。
-
如果 \(prime_j \nmid i\):
按照计算式,\(\sigma(q) = \sigma(i) \times (prime_j+1)\)。
-
如果 \(prime_j \mid i\):
分类讨论:
和筛 \(\tau\) 时的分类讨论类似,只不过需要乘上 \(prime_j\), 即 \(\sigma(q) = \sigma(i)+prime_j \times (\sigma(i)-\sigma(\dfrac{i}{prime_j}))\),因为新增的约数中 \(prime_j\) 的次数增加了。
# include <bits/stdc++.h>
const int N=10000010,INF=0x3f3f3f3f;
bool vis[N];
int sum[N];
int prime[N],tot;
int T,c;
int minx[N];
inline int read(void){
int res,f=1;
char c;
while((c=getchar())<'0'||c>'9')
if(c=='-')f=-1;
res=c-48;
while((c=getchar())>='0'&&c<='9')
res=res*10+c-48;
return res*f;
}
inline void Init(void){
minx[1]=1;
sum[1]=1;
for(int i=2;i<=N-10;++i){
if(!vis[i]){
prime[++tot]=i;
sum[i]=i+1;
}
if(sum[i]<=N-10){
if(!minx[sum[i]]){
minx[sum[i]]=i;
}
}
for(int j=1;j<=tot&&i*prime[j]<=N-10;++j){
vis[i*prime[j]]=true;
if(i%prime[j]==0){
sum[i*prime[j]]=sum[i]+(sum[i]-sum[i/prime[j]])*prime[j];
break;
}else{
sum[i*prime[j]]=sum[i]+sum[i]*prime[j];
}
}
}
return;
}
int main(void){
Init();
T=read();
while(T--){
c=read();
printf("%d\n",minx[c]?minx[c]:-1);
}
return 0;
}
莫比乌斯 Mu 函数
\(\mu(n) = \begin{cases} 0 & \exists i(p_i>1)\\ 1&n=1 \or \forall i(p_i=1) \and 2 \mid m \\ -1 & \text{otherwise.}\end{cases}\)
其中 \(n=\prod\limits_{i=1}^{m} p_i^{k_i}\)。
翻译成人话,如果 \(n\) 有质因子次数大于 \(1\),那么 \(\mu(n)=0\),否则 \(\mu(n)\) 由 \(n\) 的不同质因子个数决定,个数的奇偶分别对应 \(\mu(n)=-1\) 和 \(\mu(n)=1\)。
考虑在线性筛的过程中,如果有 \(i \mod prime_j = 0\),那么 \(\mu(q)=0\)。否则 \(\mu(q)=-\mu(i)\),因为此时质因子个数奇偶性变化。
同样,质数和 \(1\) 时的处理是平凡的。
欧拉 Phi 函数
\(\varphi(n)=\sum\limits_{1 \leq i \leq n} [\gcd(n,i)=1]\)。
即 \(1\sim n\) 中和 \(n\) 互质的数的个数。