杜教筛
这个东西的前置芝士有点多啊~~
这个东西差不多就是拿来求函数前缀和的一个东西,他能做到在 O ( n 2 3 ) O(n^{\frac 23}) O(n32) 的时间复杂度内求出答案:
S ( n ) = ∑ i = 1 n f ( i ) S(n)= \sum_{i = 1}^nf(i) S(n)=i=1∑nf(i)
欧拉函数前缀和
我们先通过求欧拉函数的前缀和来感受一下杜教筛的思想,记答案为:
S ( n ) = ∑ i = 1 n φ ( n ) S(n) = \sum_{i = 1}^n \varphi(n) S(n)=i=1∑nφ(n)
然后根据众所周知的一个公式
(
φ
∗
I
)
(
n
)
=
i
d
(
n
)
(\varphi * I)(n) = id(n)
(φ∗I)(n)=id(n),我们可以进行一下的推导:
n = φ ∗ I = ∑ d ∣ n φ ( d ) = φ ( n ) + ∑ d ∣ n ∧ d < n φ ( d ) φ ( n ) = n − ∑ d ∣ n ∧ d < n φ ( d ) S ( n ) = ∑ i = 1 n φ ( i ) = ∑ i = 1 n ( i − ∑ d ∣ i ∧ d < i φ ( d ) ) = ∑ i = 1 n i − ∑ i = 1 n ∑ d ∣ i ∧ d < i φ ( d ) = 1 2 n ( n − 1 ) − ∑ ⌊ i d ⌋ = 2 n ∑ d = 1 ⌊ n i d ⌋ φ ( d ) = 1 2 n ( n − 1 ) − ∑ i = 2 n ∑ d = 1 ⌊ n i ⌋ φ ( d ) = 1 2 n ( n − 1 ) − ∑ i = 2 n S ( ⌊ n i ⌋ ) \begin{aligned} n = \varphi * I & = \sum_{d | n}\varphi(d) = \varphi(n) + \sum_{d | n \land d < n}\varphi(d) \\ \varphi(n) & = n - \sum_{d |n \land d < n}\varphi(d)\\ S(n) = \sum_{i = 1}^n\varphi(i) & = \sum_{i = 1}^n\left(i - \sum_{d | i \land d < i}\varphi(d)\right) = \sum_{i = 1}^ni - \sum_{i = 1}^n\sum_{d | i \land d < i}\varphi(d) \\ & = \frac12n(n-1) - \sum_{\lfloor \frac id \rfloor = 2}^n\sum_{d =1}^{\left\lfloor \frac{n}{\frac id} \right\rfloor}\varphi(d) = \frac 12 n (n - 1) - \sum_{i = 2}^n\sum_{d=1}^{\lfloor \frac ni \rfloor}\varphi(d) \\ & = \frac 12 n(n - 1) - \sum_{i=2}^nS \left(\left\lfloor\frac ni \right\rfloor\right) \end{aligned} n=φ∗Iφ(n)S(n)=i=1∑nφ(i)=d∣n∑φ(d)=φ(n)+d∣n∧d<n∑φ(d)=n−d∣n∧d<n∑φ(d)=i=1∑n⎝⎛i−d∣i∧d<i∑φ(d)⎠⎞=i=1∑ni−i=1∑nd∣i∧d<i∑φ(d)=21n(n−1)−⌊di⌋=2∑nd=1∑⌊din⌋φ(d)=21n(n−1)−i=2∑nd=1∑⌊in⌋φ(d)=21n(n−1)−i=2∑nS(⌊in⌋)
这样我们就得到了:
S ( n ) = 1 2 n ( n − 1 ) − ∑ i = 2 n S ( ⌊ n i ⌋ ) S(n) = \frac 12 n (n - 1) - \sum_{i =2}^nS \left(\left\lfloor \frac ni \right\rfloor\right) S(n)=21n(n−1)−i=2∑nS(⌊in⌋)
我们发现这个式子就是一个 S ( n ) S(n) S(n) 的递推式,而且 1 2 n ( n − 1 ) \frac 12 n (n - 1) 21n(n−1) 这一项的时间复杂度是 O ( 1 ) O(1) O(1) 的,所以我们只用要计算复杂度的时候可以把这一项略去。所以我们只需要计算这个式子的时间复杂度:
S ( n ) = ∑ i = 2 n S ( ⌊ n i ⌋ ) S(n) = \sum_{i = 2}^nS(\left\lfloor \frac ni \right\rfloor) S(n)=i=2∑nS(⌊in⌋)
然后我们发现右边可以整出分块,就是有 n \sqrt{n} n 个 S ( n i ) S(\frac ni) S(in) 的取值,最后加起来还有 O ( n ) O(\sqrt{n}) O(n),所以,我们设 S ( n ) S(n) S(n) 的时间复杂度是 T ( n ) T(n) T(n),那我们就有:
T ( n ) = O ( n ) + O ( ∑ i = 2 n T ( n i ) ) T(n) = O(\sqrt{n}) + O(\sum_{i = 2}^{\sqrt{n}}T(\frac ni)) T(n)=O(n)+O(i=2∑nT(in))
再展开一层(中间的第二项太小了直接略去):
T ( n i ) = O ( n i ) + O ( ∑ k = 2 n i T ( n i k ) ) = O ( n i ) T\left(\frac ni\right) = O(\sqrt{\frac ni}) + O(\sum_{k = 2}^{\sqrt{\frac ni}}T(\frac{\frac ni}{k})) = O(\sqrt{\frac ni}) T(in)=O(in)+O(k=2∑inT(kin))=O(in)
所以我们将这个式子带回:
T ( n ) = O ( n + O ( ∑ i = 2 n n i ) = O ( ∑ i = 2 n n i ) ≈ O ( ∫ 0 n n x d x ) = O ( n 3 4 ) T(n) = O(\sqrt{n} + O(\sum_{i = 2}^{\sqrt{n}}\sqrt{\frac ni}) = O(\sum_{i = 2}^{\sqrt{n}}\sqrt{\frac ni}) \approx O(\int_0^{\sqrt{n}}\sqrt{\frac nx}dx) = O(n^{\frac 34}) T(n)=O(n+O(i=2∑nin)=O(i=2∑nin)≈O(∫0nxndx)=O(n43)
但是直接递归的做法不是最优的,我们还可以考虑先用线性筛筛出前 k k k 个 S ( n ) S(n) S(n),这样显然可以降低一些复杂度。我们考虑筛出前 k k k 个 S ( n ) S(n) S(n) 且 k ≥ n k \geq \sqrt{n} k≥n,所以我们现在只用求的是 k < ⌊ n i ⌋ ≤ n k < \lfloor \frac ni \rfloor \leq n k<⌊in⌋≤n 的部分就能解决问题了,也就是计算 1 < i ≤ n k 1 < i \leq \frac nk 1<i≤kn 的部分。那么时间复杂度就是:
T ( n ) = ∑ i = 2 n k O ( n i ) ≈ O ( ∫ 0 n k n k d x ) = O ( n k ) T(n) = \sum_{i = 2}^{\frac nk}O(\sqrt{\frac ni}) \approx O(\int_0^{\frac nk} \sqrt{\frac nk} dx) = O(\frac{n}{\sqrt{k}}) T(n)=i=2∑knO(in)≈O(∫0knkndx)=O(kn)
因为线性筛也要花时间,也就是 O ( k ) O(k) O(k) 的时间,所以总的复杂度应该为:
T ( n ) = O ( k ) + O ( n k ) T(n) = O(k) + O(\frac{n}{\sqrt{k}}) T(n)=O(k)+O(kn)
我们通过一些计算手段可以得到 k k k 差不多取到 O ( n 2 3 ) O(n^{\frac 23}) O(n32) 的时候 T ( n ) T(n) T(n) 是最小的,复杂度就是:
O ( n 2 3 + O ( n n 2 3 ) ) = O ( n 2 3 ) O(n^{\frac 23} +O(\frac {n}{\sqrt{n^{\frac 23}}})) = O(n^{\frac 23}) O(n32+O(n32n))=O(n32)
莫比乌斯函数
推导公式和计算的过程基本和欧拉函数都是差不多的在这里给出计算过程,不做详细解释:
( μ ∗ I ) ( n ) = ε ( n ) = [ n = 1 ] [ n = 1 ] = ∑ d ∣ n μ ( d ) = μ ( n ) + ∑ d ∣ n ∧ d < n μ ( d ) μ ( n ) = [ n = 1 ] − ∑ d ∣ n ∧ d < n μ ( d ) S ( n ) = ∑ i = 1 n μ ( i ) = ∑ i = 1 n ( [ i = 1 ] − ∑ d ∣ i ∧ d < i μ ( d ) ) = 1 − ∑ i = 1 n ∑ d ∣ i ∧ d < i μ ( d ) = 1 − ∑ ⌊ i d ⌋ = 2 n ∑ d = 1 ⌊ n i d ⌋ μ ( d ) = 1 − ∑ i = 2 n ∑ d = 1 ⌊ n i ⌋ μ ( d ) = 1 − ∑ i = 2 n S ( ⌊ n i ⌋ ) S ( n ) = 1 − ∑ i = 2 n S ( ⌊ n i ⌋ ) \begin{aligned} (\mu * I)&(n) = \varepsilon(n) = [n = 1] \\ [n = 1] & = \sum_{d | n}\mu(d) = \mu(n) + \sum_{d |n \land d < n}\mu(d) \\ \mu(n) & = [n = 1] - \sum_{d | n \land d < n}\mu(d) \\ S(n) = \sum_{i = 1}^n \mu(i) & = \sum_{i = 1}^n\left( [i = 1] - \sum_{d | i \land d < i}\mu(d) \right) = 1 - \sum_{i = 1}^n \sum_{d | i \land d < i}\mu(d) \\ & = 1 - \sum_{\lfloor\frac id\rfloor = 2}^n\sum_{d = 1}^{\lfloor \frac{n}{\frac id} \rfloor}\mu(d) = 1 - \sum_{i = 2}^n\sum_{d = 1}^{\lfloor \frac ni \rfloor}\mu(d) = 1 - \sum_{i = 2}^n S\left(\left\lfloor \frac ni \right\rfloor\right) \\ S(n) &= 1 - \sum_{i = 2}^nS\left(\left\lfloor \frac ni \right\rfloor\right) \end{aligned} (μ∗I)[n=1]μ(n)S(n)=i=1∑nμ(i)S(n)(n)=ε(n)=[n=1]=d∣n∑μ(d)=μ(n)+d∣n∧d<n∑μ(d)=[n=1]−d∣n∧d<n∑μ(d)=i=1∑n⎝⎛[i=1]−d∣i∧d<i∑μ(d)⎠⎞=1−i=1∑nd∣i∧d<i∑μ(d)=1−⌊di⌋=2∑nd=1∑⌊din⌋μ(d)=1−i=2∑nd=1∑⌊in⌋μ(d)=1−i=2∑nS(⌊in⌋)=1−i=2∑nS(⌊in⌋)
代码
传送门:luogu4213 杜教筛
#include<bits/stdc++.h>
using namespace std;
#define MAXN 100001000
#define ll long long
int T = 0; int n = 0;
bool vi[MAXN] = { 0 };
int pri[MAXN] = { 0 }; int m = 0;
ll ph[MAXN] = { 0 };
int mu[MAXN] = { 0 };
unordered_map<int, ll> P, M;
void Sieve(int n){
mu[1] = ph[1] = 1;
for(int i = 2; i <= n; i++){
if(!vi[i]) pri[++m] = i, ph[i] = i - 1, mu[i] = -1;
for(int j = 1; j <= m; j++){
if(i * pri[j] > n) break;
vi[i * pri[j]] = 1;
if(i % pri[j] == 0){
ph[i * pri[j]] = ph[i] * pri[j];
mu[i * pri[j]] = 0; break;
}
mu[i * pri[j]] = -mu[i];
ph[i * pri[j]] = ph[i] * (pri[j] - 1);
}
}
for(int i = 2; i <= n; i++)
mu[i] += mu[i - 1], ph[i] += ph[i - 1];
}
ll Phi(ll n){
if(n <= 1e7) return ph[n];
if(P.count(n)) return P[n];
ll x = 1ll * n * (n + 1) / 2;
for(ll l = 2, r; l <= n; l = r + 1){
int v = n / l; r = n / v;
x -= 1ll * (r - l + 1) * Phi(v);
}
return P[n] = x;
}
ll Mu(ll n){
if(n <= 1e7) return mu[n];
if(M.count(n)) return M[n];
ll x = 1;
for(ll l = 2, r; l <= n; l = r + 1){
int v = n / l; r = n / v;
x -= 1ll * (r - l + 1) * Mu(v);
}
return M[n] = x;
}
int main(){
cin >> T;
Sieve(1e7);
while(T--){
cin >> n;
M.clear(); P.clear();
cout << Phi(n) << ' ' << Mu(n) << '\n';
}
return 0;
}