杜教筛 学习笔记
杜教筛 学习笔记
膜拜 dls orz。
Dirichlet 卷积
Dirichlet 卷积,即狄利克雷卷积,对于两个数论函数 \(f, g\),定义他们的 Dirichlet 卷积 \((f * g)\) 为:
\[(f*g)(n) = \sum_{d | n} f(d)g(\dfrac{n}d)
\]
对于两个积性函数,它们的 狄利克雷卷积 也是积性函数。
Dirichlet 卷积符合交换律,结合律,分配律。
杜教筛
用于求一些数论函数 \(f\) 的前缀和:
\[S(n) = \sum_{ i= 1}^n f(i)
\]
考虑 \(h = f* g\),\(*\) 表示 Dirichlet 卷积。
则对 \(h\) 求前缀和:
\[\begin{aligned}
\sum_{i = 1}^n h(i)&= \sum_{i = 1}^n \sum_{d | i}g(d)f(\dfrac{i}d)\\
\sum_{i = 1}^n h(i)&= \sum_{d = 1}^n \sum_{d | i}^ng(d)f(\dfrac{i}d)\\
\sum_{i = 1}^n h(i)&= \sum_{d = 1}^n g(d)\sum_{d | i}^nf(\dfrac{i}d)\\
\sum_{i = 1}^n h(i)&= \sum_{d = 1}^n g(d)\sum_{i = 1}^{\lfloor\frac{n}d\rfloor}f(i)\\
\sum_{i = 1}^n h(i)&= \sum_{d = 1}^n g(d)S(\lfloor\frac{n}d\rfloor)\\
\sum_{i = 1}^n h(i)&= g(1)S(n) + \sum_{d = 2}^n g(d)S(\lfloor\frac{n}d\rfloor)\\
g(1)S(n)&= \sum_{i = 1}^n h(i) - \sum_{d = 2}^n g(d)S(\lfloor\frac{n}d\rfloor)\\
\end{aligned}
\]
可以发现,如果 \(h\) 的前缀和可快速求出,\(g\) 的前缀和可以快速求出,就可以使用数论分块递归地计算 \(S(n)\)。
如果预处理前 \(m = n^{\frac23}\) 个 \(g, h\) 的前缀和,时间复杂度可以达到 \(O(n^{\frac23})\),证明困难,略去。
例子
约定 \(\text{id}(n) = n, 1(n) = n, \varepsilon(n) = [n = 1]\)。
欧拉函数
当 \(f = \varphi\) 时,可以构造 \(\text{id}=\varphi*1\),套杜教筛公式可以得到:
\[S_\varphi(n) = \sum_{i = 1}^n 1-\sum_{d = 2}^nS_\varphi(\lfloor\frac{n}d\rfloor)\\
S_\varphi(n) = \dfrac{n(n + 1)}2 -\sum_{d = 2}^nS_\varphi(\lfloor\frac{n}d\rfloor)\\
\]
可以快速计算。
莫比乌斯函数
当 \(f = \mu\) 时,可以构造 \(\varepsilon=\mu*1\),套杜教筛公式可以得到:
\[S_\varphi(n) = \sum_{i = 1}^n [n = 1]-\sum_{d = 2}^nS_\varphi(\lfloor\frac{n}d\rfloor)\\
S_\mu(n) = 1 -\sum_{d = 2}^nS_\mu(\lfloor\frac{n}d\rfloor)\\
\]
可以快速计算。
C++ 实现
以下是 P4213 【模板】杜教筛 的代码实现。
// Problem: P4213 【模板】杜教筛
// Contest: Luogu
// Author: Moyou
// Copyright (c) 2023 Moyou All rights reserved.
// Date: 2023-11-28 23:23:03
#include <algorithm>
#include <cstring>
#include <iostream>
#include <queue>
#include <unordered_map>
#define int long long
using namespace std;
const int N = 1664520; // n^{2/3}
int primes[N], tot;
int phis[N], mus[N];
unordered_map<int, int> phi, mu;
bool st[N];
void sieve() {
mus[1] = phis[1] = 1;
for(int i = 2; i <= N - 5; i ++) {
if(!st[i]) primes[++ tot] = i, mus[i] = -1, phis[i] = i - 1;
for(int j = 1; j <= tot && primes[j] * i <= N - 5; j ++) {
st[i * primes[j]] = 1;
if(i % primes[j] == 0) {
phis[i * primes[j]] = phis[i] * primes[j];
break;
}
phis[i * primes[j]] = phis[i] * (primes[j] - 1), mus[i * primes[j]] = -mus[i];
}
}
for(int i = 1; i <= N - 5; i ++) mus[i] += mus[i - 1], phis[i] += phis[i - 1];
}
int Duphi(int n) {
if(n <= N - 5) return phis[n]; // 不加这个会被卡常 qwq
if(phi.count(n)) return phi[n];
int ans = n * (n + 1) / 2;
for(int l = 2, r; l <= n; l = r + 1) {
r = n / (n / l);
ans -= (r - l + 1) * Duphi(n / l);
}
return phi[n] = ans;
}
int Dumu(int n) {
if(n <= N - 5) return mus[n];
if(mu.count(n)) return mu[n];
int ans = 1;
for(int l = 2, r; l <= n; l = r + 1) {
r = n / (n / l);
ans -= (r - l + 1) * Dumu(n / l);
}
return mu[n] = ans;
}
int n;
void work() {
cin >> n;
cout << Duphi(n) << ' ' << Dumu(n) << '\n';
return ;
}
signed main() {
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
sieve();
int T = 1;
cin >> T;
while (T--) work();
return 0;
}