杜教筛是对某种函数快速求前缀和(不是所有,是固定参数的)的做法,经过预处理优化,一般能够优化到 O ( n 2 3 ) O(n^{\frac{2}{3}}) O(n32) 的时间复杂度。它由杜瑜皓引进,所以称为杜教筛。
原理
假设函数 f ( n ) , g ( n ) f(n),g(n) f(n),g(n) 是积性函数, h = f ∗ g h = f * g h=f∗g,则函数 h ( n ) h(n) h(n) 是积性函数。
F , G , H F,G,H F,G,H 分别是 f , g , h f,g,h f,g,h 的前缀和。
此时我们给定 n n n ,需要快速求出 F ( n ) F(n) F(n) 。
因为
h
(
n
)
=
∑
d
∣
n
f
(
d
)
⋅
g
(
n
d
)
=
f
(
n
)
⋅
g
(
1
)
+
∑
d
∣
n
,
d
<
n
f
(
d
)
⋅
g
(
n
d
)
\begin{aligned} h(n) &= \sum_{d|n}f(d)\cdot g(\frac{n}{d})\\ &=f(n)\cdot g(1)+\sum_{d|n,d<n}f(d)\cdot g(\frac{n}{d}) \end{aligned}
h(n)=d∣n∑f(d)⋅g(dn)=f(n)⋅g(1)+d∣n,d<n∑f(d)⋅g(dn)
所以
∑ i = 1 n h ( i ) = ∑ i = 1 n f ( i ) ⋅ g ( 1 ) + ∑ i = 1 n ∑ d ∣ n , d < i f ( d ) ⋅ g ( i d ) \sum_{i = 1}^n h(i)=\sum_{i = 1}^n f(i) \cdot g(1)+\sum_{i=1}^n\sum_{d|n,d<i}f(d)\cdot g(\frac{i}{d}) i=1∑nh(i)=i=1∑nf(i)⋅g(1)+i=1∑nd∣n,d<i∑f(d)⋅g(di)
此时,我们设 j = i / d j = i/d j=i/d ,则有
∑ i = 1 n h ( i ) = H ( n ) = ∑ i = 1 n f ( i ) ⋅ g ( 1 ) + ∑ j = 2 n g ( j ) ∑ d = 1 n / j f ( d ) = F ( n ) ⋅ g ( 1 ) + ∑ j = 2 n g ( j ) ⋅ F ( n j ) \begin{aligned} \sum_{i = 1}^n h(i)&=H(n)\\ &=\sum_{i = 1}^n f(i) \cdot g(1)+\sum_{j=2}^n g(j)\sum_{d = 1}^{n/j}f(d)\\ &=F(n)\cdot g(1)+\sum_{j = 2}^n g(j)\cdot F(\frac{n}{j}) \end{aligned} i=1∑nh(i)=H(n)=i=1∑nf(i)⋅g(1)+j=2∑ng(j)d=1∑n/jf(d)=F(n)⋅g(1)+j=2∑ng(j)⋅F(jn)
因为 g ( 1 ) = 1 g(1)=1 g(1)=1 ,则可以写为
F ( n ) = H ( n ) − ∑ j = 2 n g ( j ) ⋅ F ( n j ) F(n)=H(n)-\sum_{j=2}^ng(j)\cdot F(\frac{n}{j}) F(n)=H(n)−j=2∑ng(j)⋅F(jn)
如果 H ( n ) H(n) H(n) 和 G ( j ) G(j) G(j) 可以快速求(比如 O ( 1 ) O(1) O(1) 求),我们就得到了一个关于 F ( n ) F(n) F(n) 的递推式。
此时用数论分块可以降到 O ( n 3 4 ) O(n^{\frac{3}{4}}) O(n43) 的时间复杂度。
如果我们将前 n 2 3 n^{\frac{2}{3}} n32 个 F F F 数组预处理出来,则时间可以降为 O ( n 2 3 ) O(n^{\frac{2}{3}}) O(n32)
例题
例题一
求 ∑ i = 1 n ϕ ( i ) \sum_{i=1}^n \phi(i) ∑i=1nϕ(i) 和 ∑ i = 1 n μ ( i ) \sum_{i=1}^n \mu(i) ∑i=1nμ(i) 。
其中 n ≤ 2 31 − 1 n\leq2^{31}-1 n≤231−1
思路
板题,简单说说。
因为我们知道 ϕ ∗ I = I d \phi*I=Id ϕ∗I=Id ,并且 I , I d I,Id I,Id 的前缀和都可以 O ( 1 ) O(1) O(1) 求,此时就有
∑ i = 1 n ϕ ( i ) = n ⋅ ( n + 1 ) 2 − ∑ j = 2 n I ( j ) ∑ i = 1 n / j ϕ ( i ) \sum_{i=1}^{n}\phi(i) =\frac{n\cdot(n+1)}{2}-\sum_{j=2}^{n}I(j)\sum_{i=1}^{n/j}\phi(i) i=1∑nϕ(i)=2n⋅(n+1)−j=2∑nI(j)i=1∑n/jϕ(i)
此时设 f ( n ) = ∑ i = 1 n ϕ ( i ) f(n)=\sum_{i=1}^{n}\phi(i) f(n)=∑i=1nϕ(i) ,则有
f ( n ) = n ⋅ ( n + 1 ) 2 − ∑ j = 2 n I ( j ) ⋅ f ( n / j ) f(n) =\frac{n\cdot(n+1)}{2}-\sum_{j=2}^{n}I(j)\cdot f(n/j) f(n)=2n⋅(n+1)−j=2∑nI(j)⋅f(n/j)
因为 μ ∗ I = ϵ \mu*I=\epsilon μ∗I=ϵ ,所以同上推导就可以了。
这就是杜教筛的模板。
代码
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int MAXN = 5000000;
int T, n, ptot, prime[MAXN + 10], phi[MAXN + 10], mu[MAXN + 10];
bool bz[MAXN + 10];
LL premu[MAXN + 10], prephi[MAXN + 10];
map<LL, LL> mpphi, mpmu;
void get(int n) {
phi[1] = mu[1] = 1;
for (int i = 2; i <= n; i++) {
if (!bz[i])
ptot++, prime[ptot] = i, phi[i] = i - 1, mu[i] = -1;
for (int j = 1; j <= ptot && prime[j] * i <= n; j++) {
bz[prime[j] * i] = true;
if (i % prime[j] == 0) {
phi[i * prime[j]] = phi[i] * prime[j];
mu[i * prime[j]] = 0;
break;
} else {
phi[i * prime[j]] = phi[i] * (prime[j] - 1);
mu[i * prime[j]] = -mu[i];
}
}
}
for (int i = 1; i <= n; i++) premu[i] = premu[i - 1] + mu[i], prephi[i] = prephi[i - 1] + phi[i];
}
LL dfsphi(int n) {
if (n <= MAXN)
return prephi[n];
if (mpphi.count(n))//如果之前求过,就把值拿来用
return mpphi[n];
LL sum = 0;
for (int i = 2; i <= n; i++) {
int last = i - 1;
i = n / (n / i);
sum = sum + dfsphi(n / i) * (i - last);
if (i == n)//因为i加1后有可能会爆int
break;
}
return mpphi[n] = n * (n + 1ll) / 2 - sum;
}
LL dfsmu(int n) {
if (n <= MAXN)
return premu[n];
if (mpmu.count(n))//如果之前求过,就把值拿来用
return mpmu[n];
LL sum = 0;
for (int i = 2; i <= n; i++) {
int last = i - 1;
i = n / (n / i);
sum = sum + dfsmu(n / i) * (i - last);
if (i == n)//因为i加1后有可能会爆int
break;
}
return mpmu[n] = 1 - sum;
}
int main() {
scanf("%d", &T);
get(MAXN);//预处理处前n个前缀和
while (T--) {
scanf("%d", &n);
printf("%lld %lld\n", dfsphi(n), dfsmu(n));
}
return 0;
}
例题二
求 ∑ i = 1 n ϕ ( i 2 ) \sum_{i=1}^n \phi(i^2) ∑i=1nϕ(i2) 模 1 0 9 + 7 10^9+7 109+7 的结果。
其中 n ≤ 1 0 9 n\leq10^9 n≤109 。
思路
我们要知道一个性质,即 ϕ ( i 2 ) = i ⋅ ϕ ( i ) \phi(i^2)=i\cdot \phi(i) ϕ(i2)=i⋅ϕ(i) 。
所以原式就变成了 ∑ i = 1 n i ⋅ ϕ ( i ) \sum_{i=1}^n i\cdot \phi(i) ∑i=1ni⋅ϕ(i) 。
因为 n 2 = n ∑ i ∣ n p h i ( i ) = ∑ i ∣ n ϕ ( i ) ⋅ i ⋅ n i n^2=n\sum_{i|n} phi(i)=\sum_{i|n}\phi(i)\cdot i\cdot\frac{n}{i} n2=n∑i∣nphi(i)=∑i∣nϕ(i)⋅i⋅in 。
所以 n ⋅ ϕ ( n ) = n 2 − ∑ i ∣ n , i < n ϕ ( i ) ⋅ i ⋅ n i n\cdot \phi(n)=n^2-\sum_{i|n,i<n}\phi(i)\cdot i\cdot \frac{n}{i} n⋅ϕ(n)=n2−∑i∣n,i<nϕ(i)⋅i⋅in 。
此时我们设 f ( i ) = ϕ ( i 2 ) = i ⋅ ϕ ( i ) f(i)=\phi(i^2)=i\cdot \phi(i) f(i)=ϕ(i2)=i⋅ϕ(i) ,则有 f ( n ) = n 2 − ∑ i ∣ n , i < n f ( i ) ⋅ n i f(n)=n^2-\sum_{i|n,i<n}f(i)\cdot\frac{n}{i} f(n)=n2−∑i∣n,i<nf(i)⋅in 。
我们设 F ( n ) = ∑ i = 1 n f ( i ) F(n) = \sum_{i=1}^nf(i) F(n)=∑i=1nf(i) ,则
F ( n ) = ∑ i = 1 n f ( i ) = ∑ i = 1 n i 2 − ∑ i = 1 n ∑ j ∣ i , j < i f ( j ) ⋅ i j = ∑ i = 1 n i 2 − ∑ d = 2 n d ∑ i = 1 n / d f ( i ) = ∑ i = 1 n i 2 − ∑ d = 2 n d ⋅ F ( n d ) \begin{aligned} F(n)&=\sum_{i=1}^nf(i)\\ &=\sum_{i=1}^ni^2-\sum_{i=1}^n\sum_{j|i,j<i}f(j)\cdot\frac{i}{j}\\ &=\sum_{i=1}^ni^2-\sum_{d=2}^nd\sum_{i=1}^{n/d}f(i)\\ &=\sum_{i=1}^ni^2-\sum_{d=2}^nd\cdot F(\frac{n}{d}) \end{aligned} F(n)=i=1∑nf(i)=i=1∑ni2−i=1∑nj∣i,j<i∑f(j)⋅ji=i=1∑ni2−d=2∑ndi=1∑n/df(i)=i=1∑ni2−d=2∑nd⋅F(dn)
此时就用杜教筛来做就行了。
代码
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int MAXN = 5000000;
const LL MOD = 1e9 + 7;
const LL rev = 166666668;
int T, n, ptot, prime[MAXN + 10];
bool bz[MAXN + 10];
LL prephi[MAXN + 10], phi[MAXN + 10];
map<LL, LL> mpphi;
void get(int n) {
phi[1] = 1;
for (int i = 2; i <= n; i++) {
if (!bz[i])
ptot++, prime[ptot] = i, phi[i] = 1ll * (i - 1) * i % MOD;
for (int j = 1; j <= ptot && prime[j] * i <= n; j++) {
bz[prime[j] * i] = true;
if (i % prime[j] == 0) {
phi[i * prime[j]] = phi[i] * prime[j] % MOD * prime[j] % MOD;
break;
} else {
phi[i * prime[j]] = phi[i] * (prime[j] - 1) % MOD * prime[j] % MOD;
}
}
}
for (int i = 1; i <= n; i++) prephi[i] = (prephi[i - 1] + phi[i]) % MOD;
}
LL dfsphi(int n) {
if (n <= MAXN)
return prephi[n];
if (mpphi.count(n))
return mpphi[n];
LL sum = 0;
for (int i = 2; i <= n; i++) {
int last = i - 1;
i = n / (n / i);
sum = (sum + (1ll * (i - last) * (i + last + 1) / 2) % MOD * dfsphi(n / i) % MOD) % MOD;
}
return mpphi[n] = (1ll * n * (n + 1ll) % MOD * (2ll * n + 1ll) % MOD * rev % MOD - sum + MOD) % MOD;
}
int main() {
get(MAXN);//这里求的是phi(i)的平方的前缀和
scanf("%d", &n);
printf("%lld\n", dfsphi(n));
return 0;
}