杜教筛

1 积性函数和狄利克雷卷积

1.1 积性函数

1.1.1 定义

积性函数在以前的学习中遇到过很多,例如莫比乌斯函数 \(\mu(n)\),欧拉函数 \(\varphi(n)\) 等等。那么我们对积性函数定义如下:

  • 称定义域在正整数上的函数叫做数论函数(也叫算数函数)。
  • 对于一个数论函数 \(f(n)\),如果 \(f(xy)=f(x)f(y)\) 对于任意互质的 \(x,y\in\mathbb{N}_+\) 都成立,则称 \(f(n)\) 为积性函数。
  • 特别的,对于一个数论函数 \(f(n)\),如果 \(f(xy)=f(x)f(y)\) 对于任意的 \(x,y\in\mathbb{N}_+\) 都成立,则称 \(f(n)\) 为完全积性函数。

1.1.2 常见积性函数

  • 单位函数:\(\varepsilon(n)=[n=1]\)(完全积性)。
  • 恒等函数:\(\text{id}_k(n)=n^k\)(完全积性)。其中 \(\text{id}_1(n)=n\),通常简记为 \(\text{id}(n)\)
  • 常数函数:\(1(n)=1\)(完全积性)。
  • 除数函数:\(\sigma_k(n)=\sum\limits_{d\mid n} d^k\)。其中 \(\sigma_0(n)\) 即因子个数,通常简记为 \(d(n)\)\(\sigma_1(n)\) 即因子之和,通常简记为 \(\sigma(n)\)
  • 欧拉函数:\(\varphi(n)=\sum\limits_{i=1}^n[\gcd(i,n)=1]\)
  • 莫比乌斯函数:\(\mu(n)\)

1.2 狄利克雷卷积

1.2.1 定义

对于两个数论函数 \(f(x)\)\(g(x)\),它们的狄利克雷卷积得到的结果 \(h(x)\) 定义为:

\[h(x)=\sum_{d\mid x} f(d)g(\dfrac xd) \]

上式通常简记为:

\[h=f*g \]

例如在 \(1.1.2\) 中出现的一些函数,我们会有如下式子:

  • \(\varepsilon=\mu*1\)
  • \(d=1*1\)
  • \(\sigma=\text{id}*1\)
  • \(\varphi=\mu*\text{id}\)

1.2.2 性质

  • 交换律:\(f*g=g*f\)
  • 结合律:\((f*g)*h=f*(g*h)\)
  • 分配率:\((f+g)*h=f*h+g*h\)

2 杜教筛

2.1 引入

首先需要了解的前置知识:积性函数、狄利克雷卷积、数论分块。

杜教筛处理的是对于一类数论函数 \(f(n)\),求其前缀和 \(S(n)=\sum\limits_{i=1}^nf(i)\) 的值。通常情况下 \(n\) 会很大,所以线性复杂度是不可行的。我们考虑数论分块,假如我们能够构造一个 \(S(n)\) 关于 \(S(\lfloor\dfrac ni\rfloor)\) 的递推式,就可以用数论分块快速求解了。

2.2 算法思想

对于一个数论函数 \(g\),会有如下等式:

\[\begin{aligned} \sum_{i=1}^n (f*g)(i)=&\sum_{i=1}^n\sum_{d\mid i}g(d)f(\dfrac i d)\\ =&\sum_{i=1}^n\sum_{j=1}^{\lfloor\frac ni\rfloor}g(i)f(j)\\ =&\sum_{i=1}^ng(i)\sum_{j=1}^{\lfloor\frac ni\rfloor}f(j)\\ =&\sum_{i=1}^ng(i)S(\lfloor\dfrac ni\rfloor) \end{aligned} \]

于是会有如下递推式:

\[\begin{aligned} g(1)S(n)=&\sum_{i=1}^n g(i)S(\lfloor\dfrac ni\rfloor)-\sum_{i=2}^n g(i)S(\lfloor\dfrac ni\rfloor)\\ =&\sum\limits_{i=1}^n(f*g)(i)-\sum_{i=2}^n g(i)S(\lfloor\dfrac ni\rfloor) \end{aligned} \]

假如我们可以构造适当的函数 \(g\),使得:

  • 可以快速求解出 \(\sum\limits_{i=1}^n (f*g)(i)\)
  • 可以快速求解出 \(g(i)\) 的前缀和,以便后面使用数论分块求解 \(\sum\limits_{i=2}^n g(i)S(\lfloor\dfrac ni\rfloor)\)

我们就能在较短时间内求得 \(g(1)S(n)\)

具体来讲,杜教筛的时间复杂度是 \(O(n^{3/4})\) 的。如果我们可以快速求出前 \(n^{2/3}\) 项的函数值(例如使用线性筛),就可以降低复杂度至 \(O(n^{2/3})\)。值得注意的是,这个时间复杂度是基于记忆化搜索的,因此需要使用 map 等数据结构存储下 \(S(i)\) 的值。

2.3 例题

例 1:【模板】杜教筛

题意:\(\sum\limits_{i=1}^n\varphi(i)\)\(\sum\limits_{i=1}^n \mu(i)\)

先考虑求 \(S_1(n)=\sum\limits_{i=1}^n \mu(i)\)

根据狄利克雷卷积可知,\(\varepsilon=\mu*1\),所以直接构造 \(g(n)=1(n)\) 即可,于是:

\[\begin{aligned} S_1(n)=&\sum\limits_{i=1}^n \mu(i)\\ =&\sum\limits_{i=1}^n \varepsilon(i)-\sum\limits_{i=2}^n1(i)S_1(\lfloor\dfrac ni\rfloor)\\ =&1-\sum\limits_{i=2}^nS_1(\lfloor\dfrac ni\rfloor) \end{aligned} \]

然后考虑求 \(S_2(n)=\sum\limits_{i=1}^n\varphi(i)\),实际上这个用莫比乌斯反演更容易得出,最后可以转化为求 \(\mu\) 的前缀和。不过我们同样可以使用杜教筛求解。

关于欧拉函数有一个广为人知的性质:\(\sum\limits_{d\mid n}\varphi(d)=n\),即 \(\varphi*1=\text{id}\)。通过这个也就可以证明出上文提到的 \(\varphi=\mu*\text{id}\) 的结论了。

我们仍然构造 \(g(n)=1(n)\),于是:

\[\begin{aligned} S_2(n)=&\sum\limits_{i=1}^n \varphi(i)\\ =&\sum\limits_{i=1}^n \text{id}(i)-\sum\limits_{i=2}^n1(i)S_2(\lfloor\dfrac ni\rfloor)\\ =&\dfrac{n(n+1)}2-\sum\limits_{i=2}^nS_2(\lfloor\dfrac ni\rfloor) \end{aligned} \]

代码如下:

#include <bits/stdc++.h>
#include <ext/pb_ds/assoc_container.hpp>
#include <ext/pb_ds/hash_policy.hpp>
#define int long long

using namespace __gnu_pbds;
using namespace std;

const int Maxn = 2e6 + 5;
const int Inf = 2e9;

int T;
int n;

int prim[Maxn], cnt, mu[Maxn], phi[Maxn];
bool vis[Maxn];

const int N = 2e6;
void init() {
	mu[1] = 1;
	phi[1] = 1;
	for(int i = 2; i <= N; i++) {
		if(!vis[i]) {
			prim[++cnt] = i;
			mu[i] = -1;
			phi[i] = i - 1;
		}
		for(int j = 1, x; j <= cnt && (x = prim[j] * i) <= N; j++) {
			vis[x] = 1;
			if(i % prim[j] == 0) {
				mu[x] = 0;
				phi[x] = phi[i] * prim[j];
				break;
			}
			mu[x] = -mu[i];
			phi[x] = phi[i] * (prim[j] - 1);
		}
	}
	for(int i = 1; i <= N; i++) {
		mu[i] += mu[i - 1];
		phi[i] += phi[i - 1];
	}
}

gp_hash_table <int, int> smu, sphi;

int sum_mu(int x) {
	if(x <= N) return mu[x];
	if(smu[x]) return smu[x];
	int l = 2, r = 0, res = 1;
	while(l <= x) {
		r = x / (x / l);
		res -= sum_mu(x / l) * (r - l + 1);
		l = r + 1;
	}
	return smu[x] = res;
}

int sum_phi(int x) {
	if(x <= N) return phi[x];
	if(sphi[x]) return sphi[x];
	int l = 2, r = 0, res = (x + 1) * x / 2;
	while(l <= x) {
		r = x / (x / l);
		res -= sum_phi(x / l) * (r - l + 1);
		l = r + 1;
	}
	return sphi[x] = res;
}

void solve() {
	cin >> n;
	cout << sum_phi(n) << " " << sum_mu(n) << '\n';
}

signed main() {
	ios::sync_with_stdio(0);
	cin.tie(0), cout.tie(0);
	cin >> T;
	init();
	while(T--) solve();
	return 0;
}

例 2:[CQOI2015] 选数

题意: 求出在值域区间 \([L,H]\) 中选出 \(n\) 个数且它们的 \(\gcd\) 恰为 \(k\) 的方案数。

这是经典 \(\gcd\) 容斥了,设 \(f(x)\) 表示选出的 \(n\) 个数的 \(\gcd\)\(x\) 的倍数的方案数,\(g(x)\) 表示选出的 \(n\) 个数的 \(\gcd\) 恰为 \(x\) 的方案数。容易得到:

\[f(x)=\sum_{x\mid d}g(d) \]

根据莫比乌斯反演可得:

\[g(x)=\sum_{x\mid d}\mu(\dfrac dx)f(d) \]

根据定义,我们又知道 \(f(x)=(\lfloor\dfrac R x\rfloor-\lceil\dfrac Lx\rceil+1)^n=(\lfloor\dfrac R x\rfloor-\lfloor\dfrac {L-1}x\rfloor)^n\),所以我们就可以轻松得出:

\[g(x)=\sum_{x\mid d}\mu(\dfrac dx)(\lfloor\dfrac R d\rfloor-\lfloor\dfrac {L-1}d\rfloor)^n \]

但是发现这个式子无法通过数论分块去优化复杂度,因此需要另辟蹊径。考虑在原始的 \(g(x)\) 式子中不去枚举 \(d\),转而去枚举 \(\dfrac dx\),可以得到:

\[\begin{aligned} g(x)=&\sum_{i=1}^{\lfloor\frac Rk\rfloor}\mu(i)f(ki)\\ =&\sum_{i=1}^{\lfloor\frac Rk\rfloor}\mu(i)(\lfloor\dfrac R {ki}\rfloor-\lfloor\dfrac {L-1}{ki}\rfloor)^n \end{aligned} \]

\(R'=\lfloor\dfrac Rk\rfloor,L'=\lfloor\dfrac {L-1}{k}\rfloor\),则:

\[g(x)=\sum_{i=1}^{R'}\mu(i)(\lfloor\dfrac {R'} {i}\rfloor-\lfloor\dfrac {L'}{i}\rfloor)^n \]

此时整个式子就可以数论分块求解了,但是此时发现线性求 \(\mu\) 的前缀和复杂度仍然会炸,所以使用杜教筛求其前缀和即可。

例 3: [BZOJ3512] DZY Loves Math IV

题意:\(\sum\limits_{i=1}^n\sum\limits_{j=1}^m\varphi(ij)\)

观察到一个很重要的点,\(n\le 10^5\),这启示我们通过枚举 \(n\) 来求解。

考虑设 \(S(n,m)=\sum\limits_{i=1}^m\varphi(ni)\)。这个时候我们的目标是将 \(\varphi\) 里面乘积的形式拆开,不然很难做。一个初步的想法是,提取出他们都有的公共质因子,将其中一个数的所有这些质因子全部除掉,这样两个数就互质了。不过这个形式难以表达,不妨再考虑一点,即对欧拉函数产生贡献的实际上是不同的质因子,也就是说每个质因子只需要保留一个,剩下的直接乘上去即可。

此时我们考虑标准分解式,令 \(n=\prod p_i^{a_i}\),设 \(x=\prod p_i,y=\dfrac nx\),这两者均可以欧拉筛简单求出。则 \(\varphi(ni)=\varphi(xyi)=y\times \varphi(xi)\)。此时我们对后面的 \(\varphi\) 运用刚刚推出来的结论,发现此时他们的公共质因子就是 \(\gcd(x,i)\),并且让 \(x\) 除掉所有这些质因子其实就是 \(\dfrac{x}{\gcd(x,i)}\)。所以我们就可以做如下变换:

\[\begin{aligned} S(n,m)=&\sum\limits_{i=1}^m\varphi(ni)\\ =&y\sum\limits_{i=1}^m\varphi(xi)\\ =& y\sum\limits_{i=1}^m\varphi(\dfrac{x}{\gcd(x,i)})\varphi(i)\gcd(x,i)\\ =& y\sum\limits_{i=1}^m\varphi(\dfrac{x}{\gcd(x,i)})\varphi(i)\sum_{d\mid \gcd(x,i)} \varphi(d)\\ =&y\sum\limits_{i=1}^m\varphi(i)\sum_{d\mid \gcd(x,i)} \varphi(d)\varphi(\dfrac{x}{\gcd(x,i)}) \end{aligned} \]

不难发现此时 \(d\)\(\dfrac{x}{\gcd(x,i)}\) 一定互质,所以可以直接相乘。可以得到:\(\varphi(\dfrac{dx}{\gcd(x,i)})=\varphi(\dfrac{x}{\frac{\gcd(x,i)}{d}})\)。考虑到 \(\dfrac{\gcd(x,i)}{d}\) 其实就是 \(\gcd(x,i)\) 的因数,所以我们改为枚举这个值,可以得到:

\[\begin{aligned} S(n,m)=&y\sum\limits_{i=1}^m\varphi(i)\sum_{d\mid \gcd(x,i)}\varphi(\dfrac{x}{d})\\ =&y\sum_{d\mid x}\varphi(\dfrac{x}{d})\sum\limits_{d\mid i}^m\varphi(i)\\ =&y\sum_{d\mid x}\varphi(\dfrac{x}{d})\sum\limits_{i=1}^{\lfloor\frac{m}{d}\rfloor}\varphi(di)\\ =&y\sum_{d\mid x}\varphi(\dfrac{x}{d})S(d,\lfloor\dfrac{m}{d}\rfloor)\\ \end{aligned} \]

发现这个式子是一个递归的形式,我们采用类似杜教筛的形式求解这个式子即可。当然还注意递归边界,当 \(n=1\) 时返回的是 \(\sum\limits_{i=1}^m\varphi(i)\),这个直接利用杜教筛求解即可;当 \(m=0\)\(m=1\) 时返回对应值即可。

考虑一下它的时间复杂度,在 \(n=1\) 的时候我们会计算一边 \(\varphi\) 的前缀和,并且每一个 \(m\) 只算一次,实际上是 \(O(m^{2/3})\) 的。再考虑递归复杂度,发现后一项根据数论分块易得最多 \(2\sqrt m\) 种取值,而前一项由于每一次的 \(x\) 中每个质因子只有一个,所以其总的因子数量不会很多,因此可以通过。

posted @ 2024-12-01 22:04  UKE_Automation  阅读(68)  评论(0)    收藏  举报