欧拉筛&欧拉函数
忘了欧拉筛的话,可以点这里回顾
上次提到的欧拉函数ϕ(x),它可以衍生出以下几个推论:
1. ϕ(1)=1,当x是质数时ϕ(x)=x−1
2. 当i%p=0时,ϕ(i∗p)=ϕ(i)∗p
3. 当i%p≠0时,ϕ(i∗p)=ϕ(i)∗(p−1)
重点解释一下2和3:(以下不带下标的p都表示质数)
当i%p=0时,令x=i∗p,则p是i的一个约数,它必然也是i的质因数,从不大于x的整数中选出一个数,使其不能被x的质因数整除的概率仍然还是∏(1−1/pi),其中pi是i的质因数。用它乘上x就可得ϕ(x)=ϕ(i)∗p
当i%p≠0时,x使用相同定义,则p成为了x的新质因数,它不再是i的质因数,从不大于x的整数中取出一个数,使其不能被x的质因数整除的概率除了上面提到的∏(1−1/pi),还得再乘上(1−1/p),这个时候再用x去乘,分母上的p就会将x约成i,多出一项(p−1),然后i和剩下的部分组成欧拉函数ϕ(i),得ϕ(x)=ϕ(i)∗(p−1)
之所以会提到这两个性质,是因为欧拉筛中,存在i%primes[j]这样的判断。加上欧拉筛之后,不大于某个值N的所有欧拉函数值都可以在筛的过程中,利用上面的三条推论求出。如果将所有求出的欧拉函数保存起来,甚至还可以取得欧拉函数的前缀和
C++ 代码
/**
* @class
* @brief 欧拉函数前缀和求解
* class也可以用于制作仿函数,信息隐藏能力更好
*/
class EulerFunction {
public:
/**
* @brief 求解1~n的欧拉函数前缀和
* @param n 上限
* @return 该段前缀和
* 把各表的构造放在了()重载函数中,因此该仿函数只能一次性使用
*/
size_t operator()(int n) {
isComposite = new int[n + 1]();
primes = new int[n];
phi = new size_t[n + 1]();
phi[1] = 1; //记录所有欧拉函数值,phi(1)=1
for (int i = 2; i <= n; i++) {
if (!isComposite[i]) { //遇上了质数i,将其函数值赋为i-1
primes[size++] = i;
phi[i] = i - 1;
}
for (int j = 0; j < size; j++) {
if (i * primes[j] > n) {
break;
}
isComposite[i * primes[j]] = 1;
if (i % primes[j] == 0) { //之后在欧拉筛的基础上,对应求出两种不同情况的欧拉函数
phi[i * primes[j]] = phi[i] * primes[j];
break;
}
else {
phi[i * primes[j]] = phi[i] * (primes[j] - 1);
}
}
}
size_t sum = 0;
for (int i = 1; i <= n; i++) { //最后把它们累加起来
sum += phi[i];
}
return sum;
}
~EulerFunction() {
delete[] isComposite, primes, phi;
}
private:
int* isComposite = nullptr, * primes = nullptr;
size_t* phi = nullptr;
size_t size = 0;
};