杜教筛
简介
杜教筛是一个用于处理数论函数前缀和的方法,优化后它可以在 \(O(n^{\frac 2 3})\) 的时间复杂度下求解一个前缀和
原理
如果给定一个函数\(f\) ,要求计算 \(S(n)=\sum_{i=1}^nf(i)\) 。为了降低复杂度,我们想办法构造一个 \(S(n)\) 关于 \(S(\lfloor\frac{n}{i}\rfloor)\) 的递推式
对于任意一个数论函数 \(g\) ,都满足:
\[\sum_{i=1}^n (f \ast g)(i)=\sum_{i=1}^n g(i)S(\lfloor\frac{n}{i}\rfloor)
\]
证明:
\[\begin{aligned}
&\sum_{i=1}^n\sum_{d\mid i} g(d)f(\frac i d)\\
=&\sum_{i=1}^n \sum_{j=1}^{\lfloor\frac{n}{i}\rfloor}g(i)f(j)\\
=&\sum_{i=1}^n g(i)\sum_{j=1}^{\lfloor\frac{n}{i}\rfloor}f(j)\\
=&\sum_{i=1}^n g(i)S(\lfloor\frac{n}{i}\rfloor)
\end{aligned}
\]
第一个等号的式子中的 \(i,j\) 分别枚举的是原式中的 \(d,\frac{i}{d}\)
所以可以得到:
\[g(1)S(n)=\sum_{i=1}^n (f \ast g)(i)-\sum_{i=2}^n g(i)S(\lfloor\frac{n}{i}\rfloor)
\]
如果 \((f\ast g)(i)\) 的前缀和与 \(g(i)\) 的区间和都能以 \(O(1)\) 计算,那么我们就能较快求解 \(S(n)\)
时间复杂度
当 \((f\ast g)(i)\) 和 \(g(i)\) 的前缀和都能以 \(O(1)\) 求出时,复杂度为 \(O(n^{\frac 3 4})\) ,如果用线性筛预处理出前 \(n^{\frac 2 3}\) 前缀和,复杂度可以优化到 \(O(n^{\frac 2 3})\) ,此外,还可以用哈希表对大于 \(n^{\frac 2 3}\) 的数也进行记忆化
复杂度证明:参考 杜教筛时间复杂度证明 - IcMtr
模板
求 \(\varphi(x),\mu(x)\) 的前缀和
-
对于 \(\varphi(x)\) ,可以设 \(g(x)=1\) ,因为 \(\varphi \ast 1=\operatorname{id}\)
-
对于 \(\mu(x)\) ,可以设 \(g(x)=1\) ,因为 \(\mu \ast 1=\epsilon\)
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAX_23 = 2000000;
ll phi[MAX_23 + 5], mu[MAX_23 + 5];
unordered_map<int, int> s_mu;
unordered_map<int, ll> s_phi;
int cnt;
bool is_prime[MAX_23 + 5];
int prime[MAX_23 + 5];
void sieve(int n)
{
cnt = 0;
memset(is_prime, 1, sizeof(is_prime));
is_prime[1] = false;
mu[1] = phi[1] = 1;
for(int i = 2; i <= n; i++) {
if(is_prime[i]) {
prime[++cnt] = i;
mu[i] = -1;
phi[i] = i - 1;
}
for(int j = 1; j <= cnt && i * prime[j] <= n; j++) {
int t = i * prime[j];
is_prime[t] = false;
if(i % prime[j]) {
phi[t] = phi[i] * phi[prime[j]];
mu[t] = -mu[i];
} else {
phi[t] = phi[i] * prime[j];
mu[t] = 0;
break;
}
}
}
for(int i = 1; i <= n; i++) {
mu[i] = mu[i - 1] + mu[i];
phi[i] = phi[i - 1] + phi[i];
}
}
ll sum_phi(int n)
{
if(n <= MAX_23)
return phi[n];
if(s_phi[n])
return s_phi[n];
ll res = ((1ll + n) * n) / 2ll;
for(unsigned int l = 2, r; l <= n; l = r + 1) {
r = n / (n / l);
res -= (r - l + 1) * sum_phi(n / l);
}
s_phi[n] = res;
return res;
}
int sum_mu(int n)
{
if(n <= MAX_23)
return mu[n];
if(s_mu[n])
return s_mu[n];
int res = 1;
for(int l = 2, r; l <= n; l = r + 1) {
r = n / (n / l);
res -= 1ll * (r - l + 1) * sum_mu(n / l);
}
s_mu[n] = res;
return res;
}
int main()
{
sieve(MAX_23);
int T, n;
scanf("%d", &T);
while(T--) {
scanf("%d", &n);
printf("%lld %d\n", sum_phi(n), sum_mu(n));
}
return 0;
}