学习笔记:杜教筛
杜教筛
引入
-
求 \(\Phi(n) = \sum\limits_{i = 1}^{n} \phi(n)\)。
对于欧拉函数,有 \(\phi * I = id\),即 \(\phi(n) = n - \sum\limits_{d \mid n, d < n}\phi(n)\)。
所以
\[\begin{aligned} \sum_{i = 1}^n\phi(i) &= \sum_{i = 1}^n (i - \sum_{d \mid i, d < i}\phi(d))\\ &= \dfrac{n(n + 1)}{2} - \sum_{i = 2}^n\sum_{d \mid i, d < i}\phi(d)\\ &= \dfrac{n(n + 1)}{2} - \sum_{\frac{i}{d} = 2}^n\sum_{d = 1}^{\lfloor\frac{n}{\lfloor\frac{i}{d}\rfloor}\rfloor}\phi(d)\\ &= \dfrac{n(n + 1)}{2} - \sum_{i = 2}^n\sum_{d = 1}^{\lfloor\frac{n}{i}\rfloor}\phi(d)\\ &= \dfrac{n(n + 1)}{2} - \sum_{i = 2}^n\Phi(\lfloor\frac{n}{i}\rfloor)\\ \end{aligned} \]于是只需预处理 \(O(\sqrt{n})\) 个 \(\Phi(\lfloor\dfrac{n}{i}\rfloor)\),就可以算出 \(\Phi(n)\)。
第三第四步将枚举因数化为枚举倍数,是常见减小复杂度的做法。
-
求 \(M(n) = \sum\limits_{i = 1}^{n} \mu(n)\)。
和欧拉函数类似,利用 \(\mu(n) = [n = 1] - \sum\limits_{d \mid n, d < n}\mu(d)\)。
\[\begin{aligned} \sum_{i = 1}^n\mu(n) &= \sum_{i = 1}^n [i = 1] - \sum\limits_{d \mid i, d < i}\mu(d)\\ &= 1 - \sum_{i = 2}^n\sum_{d \mid i, d < i}\mu(d)\\ &= 1 - \sum_{i = 2}^n\sum_{d = 1}^{\lfloor\frac{n}{i}\rfloor}\mu(d)\\ &= 1 - \sum_{i = 2}^nM(\lfloor\frac{n}{i}\rfloor)\\ \end{aligned} \]
推导
对于一个数论函数 \(f(n)\),杜教筛可以在低于线性复杂度内求 \(S(n) = \sum\limits_{i = 1}^nf(i)\).
如果利用整除分块把相等的一起算,就加快了速度。
根据 \(f(n)\) 的性质,构造 \(S(n)\) 关于 \(S(\lfloor\dfrac{n}{i}\rfloor)\) 的递推式,方法如下。
构造两个积性函数 \(h, \ g\),满足 \(h\) 易于求和,\(g\) 易于计算 且\(h = f * g\)。
则 \(h(i) = \sum\limits_{d \mid i}g(d)\cdot f(\dfrac{i}{d})\),对 \(h(i)\) 求和,有
-
公式法求 \(\sum_{i = 1}^n\phi(n)\)。
\(h = id, \ f = \phi, \ g = I\)。
所以 \(\sum\limits_{i = 1}^n id(i) = \sum\limits_{i = 1}^n S(\lfloor\dfrac{n}{d}\rfloor)\),即 \(S(n) = \sum\limits_{i = 1}^n id(i) - \sum\limits_{i = 2}^n S(\lfloor\dfrac{n}{d}\rfloor)\)。
同理,可用 \(\epsilon = \mu * I\) 求出 \(M(n)\)。
实现
对于 \(n < N^{\frac{2}{3}}\) 预处理,否则暴力递归然后记忆化。
复杂度证明:[Auferstanden].
#include<bits/stdc++.h>
#include<bits/extc++.h>
using namespace std;
using uint = unsigned int;
using ll = long long;
constexpr int N = 3e6 + 5, V = 3e6;
__gnu_pbds::gp_hash_table<int, int> Mu;
__gnu_pbds::gp_hash_table<int, ll> Phi;
int mu[N], p[N], v[N], idx;
ll phi[N];
void init() {
phi[1] = mu[1] = 1;
for(int i = 2; i <= V; ++ i) {
if(v[i] == 0) {
mu[i] = -1;
phi[i] = i - 1;
p[++ idx] = i;
}
for(int j = 1; j <= idx && p[j] <= V / i; ++ j) {
v[i * p[j]] = 1;
if(i % p[j] == 0) {
phi[i * p[j]] = phi[i] * p[j];
mu[i * p[j]] = 0;
break;
}
mu[i * p[j]] = -mu[i];
phi[i * p[j]] = phi[i] * (p[j] - 1);
}
}
for(int i = 1; i <= V; ++ i) {
mu[i] += mu[i - 1];
phi[i] += phi[i - 1];
}
}
ll get_Phi(uint n) {
if(n <= V) return phi[n];
if(Phi.find(n) != Phi.end()) return Phi[n];
ll ans = (ll)n * (n + 1) / 2;
for(uint i = 2, j; i <= n; i = j + 1) {
if(n / i == 0) break;
j = n / (n / i);
ans -= get_Phi(n / i) * (j - i + 1);
}
return Phi[n] = ans;
}
int get_Mu(uint n) {
if(n <= V) return mu[n];
if(Mu.find(n) != Mu.end()) return Mu[n];
int ans = 1;
for(uint i = 2, j; i <= n; i = j + 1) {
if(n / i == 0) break;
j = n / (n / i);
ans -= get_Mu(n / i) * (j - i + 1);
}
return Mu[n] = ans;
}
int main() {
cin.tie(0)->sync_with_stdio(0);
init();
int T;
cin >> T;
while(T --) {
uint n; cin >> n;
cout << get_Phi(n) << ' ' << get_Mu(n) << '\n';
}
return 0;
}