【学习笔记】线性筛积性函数
0. 前言
积性函数是数论中一种极其重要的函数。它是指对于一个函数
积性函数大多数可以用线性筛质数的方法筛出来,本文将介绍几种常见的积性函数的筛法及一些拓展。
1. 线性筛质数
大佬可跳过这一部分内容。
如果我们要判断一个数
这是对于单独求一个数来说最优的解法。但如果有若干个数,你要求每个数是不是质数,
很显然的是,两个大于
首先枚举外层循环的一个数
按照这样的思路,可以写出下面的代码:
int p[N], cnt;
bool st[N];
void prime(int n)
{
for (int i = 2; i <= n; i ++ )
{
if (!st[i]) p[ ++ cnt] = i;
for (int j = 2; i * j <= n; j ++ )
st[i * j] = true;
}
}
你可能会说,这算法时间复杂度不是
没错,这份代码并不是理想的
容易发现的是,对于一个数
所以我们可以不用对于每一个数
int p[N], cnt;
bool st[N];
void prime(int n)
{
for (int i = 2; i <= n; i ++ )
if (!st[i])
{
p[ ++ cnt] = i;
for (int j = 2; i * j <= n; j ++ )
st[i * j] = true;
}
}
*其实在这份代码的内层循环中,
这样代码的时间复杂度就是
但是,这就结束了吗?不是说要线性筛质数吗?接下来就是伟大的数学家欧拉创造的线性筛质数。
例题 P3383 【模板】线性筛素数
容易发现,对于上面的埃筛,一个数可能会被筛掉多次。比如
怎么优化这一点呢?我们只需要让每个数只被它的最小质因子筛掉就行了。
先放出代码,再进行解释。
int p[N], cnt; // p[i] 表示第 i 个质数
bool st[N]; // st[i] 表示 i 是否是质数
void prime(int n)
{
for (int i = 2; i <= n; i ++ )
{
if (!st[i]) p[ ++ cnt] = i;
for (int j = 1; p[j] <= n / i; j ++ )
{
st[i * p[j]] = true;
if (i % p[j] == 0) break;
}
}
}
如果 break。
这样时间复杂度就变成了
以上内容异常重要,是接下来筛其它积性函数的基础。
2. 线性筛欧拉函数
一句介绍:
一句求解:
现在讨论怎么在线性筛质数把欧拉函数也求出来。
- 若
i 是质数,显然1 \sim i-1 都与i 互质,故\varphi(i) = i - 1 ; - 枚举
j ,找到p_j \times i ,分两种情况求解\varphi(p_j \times i) :- 若
i \bmod p_j = 0 ,也就是p_j 是i 的最小质因子。那么也就是说在原来的i 中就已经有了一个质因子p_j ,所以i 和i \times p_j 的质因子是相同的。假设i 的所有质因子是q_k ,那么i \times p_j 的质因子也是q_k ,所以有\varphi(p_j \times i) = p_j \times i \times \prod\limits_{q \mid i, q\ is\ a\ prime}\dfrac{q-1}q 。其中后面的\prod\limits_{q \mid i, q\ is\ a\ prime}\dfrac{q-1}q 就是\varphi(i) 的值,所以\varphi(p_j \times i) = p_j \times \varphi(i) ; - 若
i \bmod p_j \ne 0 ,也就是p_j 不是i 的最小质因子。那么i \times p_j 的质因子就应该比i 多一个p_j ,所以在算p_j 给\varphi(i \times p_j) 的贡献时应该\times \dfrac{p_j-1}{p_j} ,所以\varphi(i \times p_j) = p_j \times \varphi(i) \times \dfrac{p_j-1}{p_j} = \varphi(p_j \times i) = (p_j - 1) \times \varphi(i) 。还可以根据积性函数的定义,故\varphi(p_j \times i) = \varphi(p_j) \times \varphi(i) = (p_j - 1) \times \varphi(i) 。
- 若
int p[N]; // p[i] 表示第 i 个质数
bool st[N]; // st[i] 表示 i 是否是质数
int phi[N]; // phi[i] 表示 φ(i) 的值
int cnt;
void prime(int n)
{
phi[1] = 1;
for (int i = 2; i <= n; i ++ )
{
if (!st[i]) p[ ++ cnt] = i, phi[i] = i - 1;
for (int j = 1; p[j] <= n / i; j ++ )
{
st[p[j] * i] = 1;
if (i % p[j]) phi[i * p[j]] = phi[i] * p[j]; // p[j] 不是 i 的最小质因子
else // p[j] 是 i 的最小质因子
{
phi[i * p[j]] = phi[i] * (p[j] - 1);
break;
}
}
}
}
3. 线性筛莫比乌斯函数
一句介绍:
一句求解:按照定义查找
现在讨论怎么在线性筛质数把莫比乌斯函数也求出来。
- 若
i 是质数,那么i 就只有一个质因子i ,所以\mu(i) = (-1)^1 = -1 ; - 枚举
j ,找到p_j \times i ,分两种情况求解\mu(p_j \times i) :- 若
i \bmod p_j = 0 ,也就是p_j 是i 的最小质因子。设q = \dfrac i{p_j} ,那么显然有p_j \times i = q \times {p_j}^2 ,所以p_j \times i 就有了一个平方因子,所以\mu(p_j \times i) = 0 ; - 若
i \bmod p_j \ne 0 ,也就是p_j 不是i 的最小质因子。那么p_j \times i 就比i 多了一个新的质因子p_j ,故它的本质不同的质因子个数就多了1 ,所以\mu(p_j \times i) = \mu(i) \times (-1) = -\mu(i) 。也可以根据积性函数的定义,故\mu(p_j \times i) = \mu(p_j) \times \mu(i) = \mu(i) \times (-1) = -\mu(i) 。
- 若
int p[N]; // p[i] 表示第 i 个质数
bool st[N]; // st[i] 表示 i 是否是质数
int mu[N]; // mu[i] 表示 μ(i) 的值
int cnt;
void prime(int n)
{
mu[1] = 1;
for (int i = 2; i <= n; i ++ )
{
if (!st[i]) p[ ++ cnt] = i, mu[i] = -1;
for (int j = 1; p[j] <= n / i; j ++ )
{
st[p[j] * i] = 1;
if (i % p[j]) mu[p[j] * i] = -mu[i]; // p[j] 不是 i 的最小质因子
else // p[j] 是 i 的最小质因子
{
mu[i * p[j]] = 0;
break;
}
}
}
}
4. 线性筛约数个数
一句介绍:
一句求解:若
现在讨论怎么在线性筛质数把约数个数也求出来。
在线性筛约数个数时还需要额外记录一个
- 若
i 是质数,那么根据质数的定义,i 只有两个约数1 和i ,故\sigma_0(i) = 2 ,而且显然num_i = 1 ; - 枚举
j ,找到p_j \times i ,分两种情况求解\sigma_0(p_j \times i) :- 若
i \bmod p_j = 0 ,也就是p_j 是i 的最小质因子。那么i 原来有p_j 这个约数,并且是最小的约数,现在的p_j \times i 相比i 又多了一个p_j ,也就是原来的\times (p_j + 1) 变成了\times (p_j + 1 + 1) ,也就是\times (p_j + 2) 。所以求\sigma_0(p_j \times i) 时就需要先把原来(p_j + 1) 的贡献移除,在重新加上(p_j + 2) 的贡献,也就是\sigma_0(p_j \times i) = \sigma_0(i) \times \dfrac{p_j+2}{p_j+1} ; - 若
i \bmod p_j \ne 0 ,也就是p_j 不是i 的最小质因子。那么也就是说原来的i 中没有p_j 这个约数,所以把p_j \times i 分解质因数后应该有一项是p_j^1 ,剩下的是原来i 分解质因数的结果。根据约数个数的公式,将所有的指数加1 后连乘,那么就应该在原来\sigma_0(i) 的基础上\times (1+1) ,所以\sigma_0(p_j \times i) = 2 \times \sigma_0(i) 。也可以根据积性函数的定义,故\sigma_0(p_j \times i) = \sigma_0(p_j) \times \sigma_0(i) = 2 \times \sigma_0(i) 。
- 若
int p[N]; // p[i] 表示第 i 个质数
bool st[N]; // st[i] 表示 i 是否是质数
int d[N]; // d[i] 表示 i 的约数个数
int num[N]; // num[i] 表示 i 的最小质因子出现次数
int cnt;
void prime(int n)
{
d[1] = 1;
for (int i = 2; i <= n; i ++ )
{
if (!st[i]) p[ ++ cnt] = i, d[i] = 2, num[i] = 1;
for (int j = 1; p[j] <= n / i; j ++ )
{
st[p[j] * i] = 1;
if (i % p[j]) num[i * p[j]] = 1, d[i * p[j]] = d[i] * 2; // p[j] 不是 i 的最小质因子
else // p[j] 是 i 的最小质因子
{
num[i * p[j]] = num[i] + 1, d[i * p[j]] = d[i] / (num[i * p[j]] + 1) * (num[i * p[j]] + 2);
break;
}
}
}
}
5. 线性筛约数和
一句介绍:
一句求解:若
现在讨论怎么在线性筛质数把约数和也求出来。
在线性筛约数个数时也需要额外记录一个
- 若
i 是质数,那么根据质数的定义,i 只有两个约数1 和i ,故\sigma_1(i) = 1 + i ,而且显然num_i 也是1 + i ; - 枚举
j ,找到p_j \times i ,分两种情况求解\sigma_1(p_j \times i) :- 若
i \bmod p_j = 0 ,也就是p_j 是i 的最小质因子。那么i 原来有p_j 这个约数,并且是最小的约数,所以p_j = p_1 。而现在的p_j \times i 相比i 又多了一个p_j ,也就是原来的\times \sum\limits_{k=0}^{c_1}p_j^k 变成了\times \sum\limits_{k=0}^{c_1 + 1}p_j^k 。所以求\sigma_1(p_j \times i) 时就需要先把原来\sum\limits_{k=0}^{c_1}p_j^k 的贡献移除,在重新加上\sum\limits_{k=0}^{c_1 + 1}p_j^k 的贡献,而又有\sum\limits_{k=0}^{c_1 + 1}p_j^k = \sum\limits_{k=0}^{c_1}p_j^k \times p_j + 1 ,所以\sigma_1(p_j \times i) = \sigma_1(i) \times \dfrac{num_i \times p_j + 1}{num_i} ; - 若
i \bmod p_j \ne 0 ,也就是p_j 不是i 的最小质因子。那么也就是说原来的i 中没有p_j 这个约数,所以把p_j \times i 分解质因数后应该有一项是p_j^1 ,剩下的是原来i 分解质因数的结果。根据约数和的公式,应该在原来\sigma_1(i) 的基础上\times (1+p_j^1) ,所以\sigma_1(p_j \times i) = (p_j + 1) \times \sigma_1(i) 。也可以根据积性函数的定义,故\sigma_1(p_j \times i) = \sigma_1(p_j) \times \sigma_1(i) = (p_j + 1) \times \sigma_1(i) 。
- 若
int p[N]; // p[i] 表示第 i 个质数
bool st[N]; // st[i] 表示 i 是否是质数
int d[N]; // d[i] 表示 i 的约数和
int num[N]; // num[i] 表示 i 的最小质因子的贡献和
void prime(int n)
{
d[1] = 1;
for (int i = 2; i <= n; i ++ )
{
if (!st[i]) p[ ++ cnt] = i, d[i] = num[i] = i + 1;
for (int j = 1; p[j] <= n / i; j ++ )
{
st[p[j] * i] = true;
if (i % p[j]) d[p[j] * i] = d[i] * (p[j] + 1); // p[j] 不是 i 的最小质因子
else // p[j] 是 i 的最小质因子
{
d[p[j] * i] = d[i] / num[i] * (num[i] * p[j] + 1);
num[p[j] * i] = num[i] * p[j] + 1;
break;
}
}
}
}
6. 总结及参考文献
在数论中,积性函数几乎无处不在。熟练掌握它们的求解过程乃重中之重。
参考文献:
- OI - Wiki : https://oi-wiki.org/math/number-theory/sieve/;
- 线性筛求约数个数以及约数和 : https://blog.csdn.net/calculate23/article/details/89513721;
- 一些积性函数 : https://blog.csdn.net/weixin_30244889/article/details/95661524;
- ChatGPT。
完。