杜教筛
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)\) 定义为:
上式通常简记为:
例如在 \(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\),会有如下等式:
于是会有如下递推式:
假如我们可以构造适当的函数 \(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)\) 即可,于是:
然后考虑求 \(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)\),于是:
代码如下:
#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)=(\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)\) 式子中不去枚举 \(d\),转而去枚举 \(\dfrac dx\),可以得到:
令 \(R'=\lfloor\dfrac Rk\rfloor,L'=\lfloor\dfrac {L-1}{k}\rfloor\),则:
此时整个式子就可以数论分块求解了,但是此时发现线性求 \(\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)}\)。所以我们就可以做如下变换:
不难发现此时 \(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)\) 的因数,所以我们改为枚举这个值,可以得到:
发现这个式子是一个递归的形式,我们采用类似杜教筛的形式求解这个式子即可。当然还注意递归边界,当 \(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\) 中每个质因子只有一个,所以其总的因子数量不会很多,因此可以通过。

浙公网安备 33010602011771号