线性筛积性函数
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;
}
}
*其实在这份代码的内层循环中,
这样代码的时间复杂度就是
但是,这就结束了吗?不是说要线性筛质数吗?接下来就是伟大的数学家欧拉创造的线性筛质数。
容易发现,对于上面的埃筛,一个数可能会被筛掉多次。比如
怎么优化这一点呢?我们只需要让每个数只被它的最小质因子筛掉就行了。
先放出代码,再进行解释。
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. 线性筛欧拉函数
一句介绍:
一句求解:
现在讨论怎么在线性筛质数把欧拉函数也求出来。
- 若
是质数,显然 都与 互质,故 ; - 枚举
,找到 ,分两种情况求解 :- 若
,也就是 是 的最小质因子。那么也就是说在原来的 中就已经有了一个质因子 ,所以 和 的质因子是相同的。假设 的所有质因子是 ,那么 的质因子也是 ,所以有 。其中后面的 就是 的值,所以 ; - 若
,也就是 不是 的最小质因子。那么 的质因子就应该比 多一个 ,所以在算 给 的贡献时应该 ,所以 。还可以根据积性函数的定义,故 。
- 若
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. 线性筛莫比乌斯函数
一句介绍:
一句求解:按照定义查找
现在讨论怎么在线性筛质数把莫比乌斯函数也求出来。
- 若
是质数,那么 就只有一个质因子 ,所以 ; - 枚举
,找到 ,分两种情况求解 :- 若
,也就是 是 的最小质因子。设 ,那么显然有 ,所以 就有了一个平方因子,所以 ; - 若
,也就是 不是 的最小质因子。那么 就比 多了一个新的质因子 ,故它的本质不同的质因子个数就多了 ,所以 。也可以根据积性函数的定义,故 。
- 若
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. 线性筛约数个数
一句介绍:
一句求解:若
现在讨论怎么在线性筛质数把约数个数也求出来。
在线性筛约数个数时还需要额外记录一个
- 若
是质数,那么根据质数的定义, 只有两个约数 和 ,故 ,而且显然 ; - 枚举
,找到 ,分两种情况求解 :- 若
,也就是 是 的最小质因子。那么 原来有 这个约数,并且是最小的约数,现在的 相比 又多了一个 ,也就是原来的 变成了 ,也就是 。所以求 时就需要先把原来 的贡献移除,在重新加上 的贡献,也就是 ; - 若
,也就是 不是 的最小质因子。那么也就是说原来的 中没有 这个约数,所以把 分解质因数后应该有一项是 ,剩下的是原来 分解质因数的结果。根据约数个数的公式,将所有的指数加 后连乘,那么就应该在原来 的基础上 ,所以 。也可以根据积性函数的定义,故 。
- 若
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. 线性筛约数和
一句介绍:
一句求解:若
现在讨论怎么在线性筛质数把约数和也求出来。
在线性筛约数个数时也需要额外记录一个
- 若
是质数,那么根据质数的定义, 只有两个约数 和 ,故 ,而且显然 也是 ; - 枚举
,找到 ,分两种情况求解 :- 若
,也就是 是 的最小质因子。那么 原来有 这个约数,并且是最小的约数,所以 。而现在的 相比 又多了一个 ,也就是原来的 变成了 。所以求 时就需要先把原来 的贡献移除,在重新加上 的贡献,而又有 ,所以 ; - 若
,也就是 不是 的最小质因子。那么也就是说原来的 中没有 这个约数,所以把 分解质因数后应该有一项是 ,剩下的是原来 分解质因数的结果。根据约数和的公式,应该在原来 的基础上 ,所以 。也可以根据积性函数的定义,故 。
- 若
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。
完。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!