线性筛积性函数

0. 前言

积性函数是数论中一种极其重要的函数。它是指对于一个函数 \(f(x)\),如果 \(\gcd(x, y) = 1\),则 \(f(xy) = f(x)f(y)\),则 \(f(x)\) 就是一个积性函数。

积性函数大多数可以用线性筛质数的方法筛出来,本文将介绍几种常见的积性函数的筛法及一些拓展。

1. 线性筛质数

大佬可跳过这一部分内容。

如果我们要判断一个数 \(n\) 是不是质数,那么很朴素的想法就是 \(\Theta(\sqrt n)\) 的复杂度找 \(2 \sim \sqrt n\) 中有没有 \(n\) 的约数。

这是对于单独求一个数来说最优的解法。但如果有若干个数,你要求每个数是不是质数,\(\Theta(n\sqrt n)\) 那就显得有些慢了。所以伟大的数学家埃拉托斯特尼发明了一种 \(\Theta(n\ln \ln n)\) 的筛法——埃氏筛法。

很显然的是,两个大于 \(1\) 的整数的乘积一定不是质数。埃氏筛法就是根据这一点筛质数的。

首先枚举外层循环的一个数 \(i\),然后把 \(i\) 的倍数全部筛掉,这样枚举完所有的 \(i\) 后,剩下的数就一定都是质数了。

按照这样的思路,可以写出下面的代码:

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;
	}
}

你可能会说,这算法时间复杂度不是 \(\Theta (n \log n)\) 吗,怎么就是 \(\Theta(n\ln \ln n)\) 了?

没错,这份代码并不是理想的 \(\Theta(n\ln \ln n)\),我们要对其进行优化。

容易发现的是,对于一个数 \(x\),如果它有小于 \(x\) 的某个质因子,那么它一定是合数。

所以我们可以不用对于每一个数 \(i\) 都去寻找它的倍数,只需要找所有质数的倍数即可。

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;
		}
}

*其实在这份代码的内层循环中,\(j\) 的初始值可以赋成 \(i\),这样也算是一个小优化。但效果微乎其微,且与本文主旨不符,遂不作说明。

这样代码的时间复杂度就是 \(\Theta(n\ln \ln n)\) 了。具体的为什么是这个值去问数学老师吧。

但是,这就结束了吗?不是说要线性筛质数吗?接下来就是伟大的数学家欧拉创造的线性筛质数。

例题 P3383 【模板】线性筛素数

容易发现,对于上面的埃筛,一个数可能会被筛掉多次。比如 \(12\) 会被 \(2\) 筛掉,又会被 \(3\) 筛掉,这样的重复操作会使时间复杂度带上一个 \(\ln\ln n\)

怎么优化这一点呢?我们只需要让每个数只被它的最小质因子筛掉就行了。

先放出代码,再进行解释。

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;
		}
	}
}

如果 \(i\) 之前被 \(p_j\) 筛过了,又由于 \(p\) 里面质数是从小到大的,所以 \(i\) 乘上其他的质数的结果一定会被 \(p_j\) 的倍数筛掉,就不需要在这里先筛一次,所以这里直接 break

这样时间复杂度就变成了 \(\Theta(n)\)

以上内容异常重要,是接下来筛其它积性函数的基础。

2. 线性筛欧拉函数

一句介绍:\(\varphi(x) = \sum\limits_{k = 1}^x[\gcd(k, x) = 1],\)表示 \(1 \sim x\) 中与 \(x\) 互质的数。

一句求解:\(\varphi(n) = n \times \prod\limits_{p \mid n, p\ is\ a\ prime} \dfrac {p-1}p\)

现在讨论怎么在线性筛质数把欧拉函数也求出来。

  • \(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. 线性筛莫比乌斯函数

一句介绍:\(\mu(n) = \left\{\begin{matrix}1 & n = 1\\ 0 & n 含有平方因子\\ (-1)^{本质不同质因子个数} & \mathrm{otherwise} \end{matrix}\right.\)

一句求解:按照定义查找 \(n\) 的质因子个数。

现在讨论怎么在线性筛质数把莫比乌斯函数也求出来。

  • \(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. 线性筛约数个数

一句介绍:\(\sigma_0(n)\) 表示 \(n\) 的约数个数。

一句求解:若 \(n = \prod\limits_{i=1}^mp_i^{c_i}\),那么根据乘法原理,有 \(\sigma_0(n) = \prod\limits_{i=1}^m(c_i+1)\)

现在讨论怎么在线性筛质数把约数个数也求出来。

在线性筛约数个数时还需要额外记录一个 \(num_i\) 数组,表示 \(i\) 的最小质因子的出现次数。

  • \(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. 线性筛约数和

一句介绍:\(\sigma_1(n)\) 表示 \(n\) 的约数和。

一句求解:若 \(n = \prod\limits_{i=1}^mp_i^{c_i}\),有 \(\sigma_1(n) = \prod\limits_{i=1}^m\sum\limits_{j=0}^{c_i}p_i^j\)

现在讨论怎么在线性筛质数把约数和也求出来。

在线性筛约数个数时也需要额外记录一个 \(num_i\) 数组,表示 \(i\) 的最小质因子的贡献和(若 \(i\) 的最小质因子是 \(p_1\),共出现了 \(c_1\) 次,那么 \(num_i = \sum\limits_{j=0}^{c_1}p_1^j\))。

  • \(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. 总结及参考文献

在数论中,积性函数几乎无处不在。熟练掌握它们的求解过程乃重中之重。

参考文献:

  1. OI - Wiki : https://oi-wiki.org/math/number-theory/sieve/
  2. 线性筛求约数个数以及约数和 : https://blog.csdn.net/calculate23/article/details/89513721
  3. 一些积性函数 : https://blog.csdn.net/weixin_30244889/article/details/95661524
  4. ChatGPT。

完。

posted @ 2024-03-24 17:15  2huk  阅读(62)  评论(0编辑  收藏  举报