先上板子,方便整理板子的时候使用它。后面再讲为什么。
#include<cstdio>
using namespace std;
#define IL inline
typedef long long LL;
const int N = 1e6 + 3;
bool np[N];
int pri[N],phi[N],mu[N],di[N],si[N];
IL int Euler_sieve(int n) {
int tot = 0;
phi[0] = mu[0] = di[0] = si[0] = 0;
np[1] = phi[1] = mu[1] = di[1] = si[1] = 1;
for(int i=2;i<=n;i++) {
if(!np[i]) {
pri[++tot] = i;
phi[i] = i-1;
mu[i] = -1;
di[i] = 2;
si[i] = i + 1;
}
for(int j=1;j<=tot&&i*pri[j]<=n;j++) {
np[i*pri[j]] = true;
if(i % pri[j] == 0) {
phi[i*pri[j]] = pri[j] * phi[i];
mu[i*pri[j]] = 0;
di[i*pri[j]] = (di[i] << 1) - di[i/pri[j]];
si[i*pri[j]] = si[i] + pri[j] * (si[i]-si[i/pri[j]]);
break;
}
phi[i*pri[j]] = phi[i] * (pri[j]-1);
mu[i*pri[j]] = -mu[i];
di[i*pri[j]] = di[i] << 1;
si[i*pri[j]] = si[i] * (1+pri[j]);
}
}
return tot;
}
变量名称解释:
- tot:质数个数,函数Euler_sieve(int n)也会返回质数个数。
- pri[i]:意为prime,这个数组里存储小于n的质数。
- np[i]:意为not prime。这个bool数组是判断i是否为质数的。
- phi[i]:即 \(\varphi_i\)。即为\(i\)的欧拉函数
- mu[i]:即 \(\mu_i\)。即为\(i\)的莫比乌斯函数
- di[i]:意为divisor。即为\(i\)的约数个数函数
- si[i]:即 \(\sigma_i\)。即为\(i\)的约数和函数
输入:n,意义见输出。
输出:小于等于n的所有质数以及质数个数。小于等于n的所有自然数的欧拉函数、莫比乌斯函数、约数个数函数、约数和函数。
建议已经学会了Eratosthenes筛法的同学继续往下读。
什么是欧拉筛?欧拉筛的原理是什么?
欧拉筛是对于Eratosthenes筛法的优化。Eratosthenes筛法中由于重复筛了合数许多次,导致了它的复杂度是\(O(nloglogn)\)。那么欧拉筛的优化是对每个合数而言,只会用这个合数的最小质因子筛这个合数一次,这样复杂度就优化到了\(O(n)\),你就可以线性时间筛出质数以及各种数论函数了。
接下来我们分三个部分去理解欧拉筛。第一个部分是欧拉筛是怎么筛质数的,第二个部分是积性数论函数是什么,第三个部分是欧拉筛是怎么推出积性数论函数的。
欧拉筛是怎么筛质数的?
欧拉筛是在Eratosthenes筛法基础上,利用每个正整数的最小质因数来筛质数的。显然可知,每个正整数的最小质因数有且只有一个,所以在欧拉筛的算法中,每个正整数只会被筛到一遍。在代码中的体现是,枚举当前的数字\(i\),用这个数字\(i\)去筛它的倍数。在枚举\(i\)的循环中再枚举小于\(i\)的质数\(prime_j\)。\(i*prime_j\)显然是合数,我们遍历到直接在\(np\)数组里打标记就好。如果此时\(i\)是\(prime_j\)的倍数,也即是说\(prime_j\)是\(i\)的最小质因数,那么\(i*prime_j\)的最小质因数也显然是\(prime_j\),此时就不必再枚举质数了,直接break出去。为什么这个时候要break出去呢?因为此时你找到了i的最小质因数\(prime_j\),而如果你接着遍历质数的话,就不是用最小质因数去筛质数,也无法保证算法的复杂度是严格线性的。
积性数论函数是什么? (这段照抄oiwiki,原网址:https://oi-wiki.org/math/mobius/
定义
若 \(x \bot y\) 且 \(f(xy)=f(x)f(y)\) ,则\(f(n)\)为积性函数。(\(x \bot y \iff gcd(x,y)==1\))
性质
若\(f(x)\)和\(g(x)\)都为积性函数,则以下函数也为积性函数。
例子
- 单位函数:\(\epsilon(n)=1[n=1]\)。
- 恒等函数:\(id_k(n)=n^k\)。\(id_1(n)\)通常简记作\(id(n)\)。
- 常数函数:\(1(n)=1\)。
- 除数函数:\(\sigma_k(n)=\sum_{d|n}d^k\)。\(\sigma_0(n)\)通常简记作\(d(n)\)或\(\tau(n)\),这是约数个数函数。\(\sigma_1(n)\)通常简记作\(\sigma(n)\),这是约数和函数。
- 欧拉函数:\(\varphi(n)=\sum_{i=1}^n[gcd(i,n)=1]\)
- 莫比乌斯函数:
其中\(\omega(n)\)表示n的不同质因子个数,是一个加性函数。
欧拉筛是怎么推出积性数论函数的?
其实都是基于使用最小质因子去筛合数的条件去推的。可以发现,在欧拉筛中有三种情况,第一种是\(i\)这个数自己本身就是质数,第二种\(i\)不是\(prime_j\)的倍数,第三种\(i\)是\(prime_j\)的倍数。我们在接下来的欧拉筛筛函数过程中,都会分这三种情况讨论。
接下来的这个条件,在接下来的推法中都会用到。设一个数n,它的唯一分解式为:$$n=p_1{\alpha_1}p_2{\alpha_2}...p_s^{\alpha_s}$$
由于欧拉筛是用每个数的最小质因子去筛它,所以显然的,在\(prime_j|i\)时$$prime_j=p_1$$
众所周知,这里每一个积性数论函数都有它自己通过自己质因子推出来的公式,我就不证这个公式直接用了。(逃
欧拉函数
众所周知,欧拉函数的公式是
我们要通过这个公式,推导出在欧拉筛中\(\varphi(n)\)函数的筛法。
- 第一种情况,\(i\)这个数自己本身就是质数时。\(\varphi(i)=n-1\)。
- 第二种情况,\(i\)不是\(p_1\)的倍数,说明\(i \bot p_1\)。由于欧拉函数是积性函数,由积性函数定义可得\(\varphi(i*p_1)=\varphi(i)*\varphi(p_1)=\varphi(i)*(p_1-1)\)
- 第三种情况,\(i\)是\(p_1\)的倍数。观察它原来的公式,你发现\(\frac{\varphi(i*p_1)}{\varphi(i)}=\frac{i*p_1}{i}=p_1\),于是你得到了关系\(\varphi(i*p_1)=\varphi(i)*p_1\)
莫比乌斯函数
众所周知,莫比乌斯函数的公式是
其中\(\omega(n)\)表示n的不同质因子个数,是一个加性函数。
我们要通过这个公式,推导出在欧拉筛中\(\mu(n)\)函数的筛法。
- 第一种情况,\(i\)这个数自己本身就是质数时。\(\mu(i)=-1\)。
- 第二种情况,\(i\)不是\(p_1\)的倍数,说明\(i \bot p_1\)。由于莫比乌斯函数是积性函数,由积性函数定义可得\(\mu(i*p_1)=\mu(i)*\mu(p_1)=-\mu(i)\)
- 第三种情况,\(i\)是\(p_1\)的倍数。这种情况显然在原公式中\(\exists d:d^2|n\)情况中。显然\(\mu(i*p_1)=0\)
约数个数函数
众所周知,约数个数函数的公式是
我们要通过这个公式,推导出在欧拉筛中\(d(n)\)函数的筛法。
- 第一种情况,\(i\)这个数自己本身就是质数时。\(d(i)=2\)。
- 第二种情况,\(i\)不是\(p_1\)的倍数,说明\(i \bot p_1\)。由于约数个数函数是积性函数,由积性函数定义可得\(d(i*p_1)=d(i)*d(p_1)=2d(i)\)
- 第三种情况,\(i\)是\(p_1\)的倍数。利用原公式推导一下:
约数和函数
众所周知,约数和函数的公式是
我们要通过这个公式,推导出在欧拉筛中\(\sigma(n)\)函数的筛法。
- 第一种情况,\(i\)这个数自己本身就是质数时。\(\sigma(i)=i+1\)。
- 第二种情况,\(i\)不是\(p_1\)的倍数,说明\(i \bot p_1\)。由于约数和函数是积性函数,由积性函数定义可得 \(\sigma(i*p_1)=\sigma(i)*\sigma(p_1)=\sigma(i)*(p_1+1)\)
- 第三种情况,\(i\)是\(p_1\)的倍数。利用原公式推导一下:
设
由此可以推出下面两个式子
即$$p_1\sigma(\frac{i}{p_1})+R=\sigma(i)$$
由此得到
于是,这样我熬夜爆肝终于写完了欧拉筛这四个积性函数的办法及证明。
我看别人可以有把约数个数函数不依靠其他辅助数组自己递推的做法,于是我想了想,约数和函数有没有自己递推的做法呢?于是这样我把它推了出来。为了纪念这个事情,特意熬夜爆肝写了一个巨长无比的blog。
以上。